Merge "Center Glanceable Hub in Portrait Mode." into main
diff --git a/api/Android.bp b/api/Android.bp
index cd1997c..6a04f0d 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -368,8 +368,6 @@
"--hide CallbackInterface",
// Disable HiddenSuperclass, as Metalava handles this fine (it should be hidden by default)
"--hide HiddenSuperclass",
- "--hide-package android.audio.policy.configuration.V7_0",
- "--hide-package com.android.server",
"--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
],
filter_packages: packages_to_document,
diff --git a/core/api/current.txt b/core/api/current.txt
index 46f426b..9ad8dd3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1058,7 +1058,7 @@
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
- field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final int languageSettingsActivity;
+ field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity;
field public static final int languageTag = 16844040; // 0x1010508
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
@@ -6844,6 +6844,9 @@
method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
}
+ @FlaggedApi("android.app.api_rich_ongoing") public abstract static class Notification.RichOngoingStyle extends android.app.Notification.Style {
+ }
+
public abstract static class Notification.Style {
ctor @Deprecated public Notification.Style();
method public android.app.Notification build();
@@ -11394,7 +11397,7 @@
field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
- field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
+ field public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
@@ -11603,6 +11606,7 @@
method public static android.content.IntentSender readIntentSenderOrNullFromParcel(android.os.Parcel);
method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler) throws android.content.IntentSender.SendIntentException;
method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler, String) throws android.content.IntentSender.SendIntentException;
+ method @FlaggedApi("com.android.window.flags.bal_send_intent_with_options") public void sendIntent(@Nullable android.content.Context, int, @Nullable android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable java.util.concurrent.Executor, @Nullable android.content.IntentSender.OnFinished) throws android.content.IntentSender.SendIntentException;
method public static void writeIntentSenderOrNullToParcel(android.content.IntentSender, android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.IntentSender> CREATOR;
@@ -12043,6 +12047,7 @@
field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
field public static final int COLOR_MODE_HDR = 2; // 0x2
field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+ field @FlaggedApi("android.content.res.handle_all_config_changes") public static final int CONFIG_ASSETS_PATHS = -2147483648; // 0x80000000
field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
@@ -12056,6 +12061,7 @@
field public static final int CONFIG_MNC = 2; // 0x2
field public static final int CONFIG_NAVIGATION = 64; // 0x40
field public static final int CONFIG_ORIENTATION = 128; // 0x80
+ field @FlaggedApi("android.content.res.handle_all_config_changes") public static final int CONFIG_RESOURCES_UNUSED = 134217728; // 0x8000000
field public static final int CONFIG_SCREEN_LAYOUT = 256; // 0x100
field public static final int CONFIG_SCREEN_SIZE = 1024; // 0x400
field public static final int CONFIG_SMALLEST_SCREEN_SIZE = 2048; // 0x800
@@ -56332,7 +56338,7 @@
public final class InputMethodInfo implements android.os.Parcelable {
ctor public InputMethodInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
ctor public InputMethodInfo(String, String, CharSequence, String);
- method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") @Nullable public android.content.Intent createImeLanguageSettingsActivityIntent();
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @Nullable public android.content.Intent createImeLanguageSettingsActivityIntent();
method @Nullable public android.content.Intent createStylusHandwritingSettingsActivityIntent();
method public int describeContents();
method public void dump(android.util.Printer, String);
@@ -56353,7 +56359,7 @@
method public boolean supportsStylusHandwriting();
method public boolean suppressesSpellChecker();
method public void writeToParcel(android.os.Parcel, int);
- field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final String ACTION_IME_LANGUAGE_SETTINGS = "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
+ field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final String ACTION_IME_LANGUAGE_SETTINGS = "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
field public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b384326..36b1eab 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -30,6 +30,7 @@
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -41,7 +42,6 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -6520,6 +6520,13 @@
return true;
}
+ if (android.content.res.Flags.handleAllConfigChanges()) {
+ if ((handledConfigChanges & CONFIG_RESOURCES_UNUSED) != 0) {
+ // Report the change if activities claim they do not use resources at all.
+ return true;
+ }
+ }
+
final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,
newConfig, sizeBuckets);
// Compare to the diff which filter the change without crossing size buckets with
@@ -6846,9 +6853,6 @@
}
private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
- if (!activityWindowInfoFlag()) {
- return;
- }
if (r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
return;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db979a5..ef09dc4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -10979,6 +10979,18 @@
}
/**
+ * An object that can apply a rich ongoing notification style to a {@link Notification.Builder}
+ * object.
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public abstract static class RichOngoingStyle extends Notification.Style {
+ /**
+ * @hide
+ */
+ public RichOngoingStyle() {}
+ }
+
+ /**
* Notification style for custom views that are decorated by the system
*
* <p>Instead of providing a notification that is completely custom, a developer can set this
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 0c1e7a3..c281533 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -19,8 +19,6 @@
import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
-
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
@@ -102,9 +100,6 @@
*/
public void registerActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
- if (!activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
mActivityWindowInfoChangedListeners.add(listener);
}
@@ -116,9 +111,6 @@
*/
public void unregisterActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
- if (!activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
mActivityWindowInfoChangedListeners.remove(listener);
}
@@ -130,9 +122,6 @@
*/
public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
@NonNull ActivityWindowInfo activityWindowInfo) {
- if (!activityWindowInfoFlag()) {
- return;
- }
final Object[] activityWindowInfoChangedListeners;
synchronized (mLock) {
if (mActivityWindowInfoChangedListeners.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/core/java/android/audio/policy/configuration/V7_0/package-info.java
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to core/java/android/audio/policy/configuration/V7_0/package-info.java
index c92234c..8f7425f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/core/java/android/audio/policy/configuration/V7_0/package-info.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+/**
+ * Hide the android.audio.policy.configuration.V7_0 API as that is managed
+ * separately.
+ *
+ * @hide
+ */
+package android.audio.policy.configuration.V7_0;
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index d05d23c..55278f6 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -32,6 +32,13 @@
}
flag {
+ namespace: "virtual_devices"
+ name: "media_projection_keyguard_restrictions"
+ description: "Auto-stop MP when the device locks"
+ bug: "348335290"
+}
+
+flag {
namespace: "virtual_devices"
name: "virtual_display_insets"
description: "APIs for specifying virtual display insets (via cutout)"
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 87fb843..385d6cfd 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -61,6 +61,7 @@
import android.permission.PermissionCheckerManager;
import android.provider.MediaStore;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseBooleanArray;
@@ -486,6 +487,8 @@
validateIncomingAuthority(authority);
int numOperations = operations.size();
final int[] userIds = new int[numOperations];
+ final ArraySet<String> readPermissions = new ArraySet<String>();
+ final ArraySet<String> writePermissions = new ArraySet<String>();
for (int i = 0; i < numOperations; i++) {
ContentProviderOperation operation = operations.get(i);
Uri uri = operation.getUri();
@@ -499,17 +502,19 @@
}
final AttributionSource accessAttributionSource =
attributionSource;
- if (operation.isReadOperation()) {
+ if (operation.isReadOperation() && !readPermissions.contains(uri.toString())) {
if (enforceReadPermission(accessAttributionSource, uri)
!= PermissionChecker.PERMISSION_GRANTED) {
throw new OperationApplicationException("App op not allowed", 0);
}
+ readPermissions.add(uri.toString());
}
- if (operation.isWriteOperation()) {
+ if (operation.isWriteOperation() && !writePermissions.contains(uri.toString())) {
if (enforceWritePermission(accessAttributionSource, uri)
!= PermissionChecker.PERMISSION_GRANTED) {
throw new OperationApplicationException("App op not allowed", 0);
}
+ writePermissions.add(uri.toString());
}
}
traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "applyBatch: ", authority);
@@ -2774,6 +2779,10 @@
+ " provider from user:" + mContext.getUserId());
}
if (userId != UserHandle.USER_CURRENT
+ // getUserIdFromAuthority can return USER_NULL when can't cast the userId to
+ // an int, which can cause high volume of binder calls.
+ && (!android.multiuser.Flags.fixGetUserPropertyCache()
+ || userId != UserHandle.USER_NULL)
&& userId != mContext.getUserId()
// Since userId specified in content uri, the provider userId would be
// determined from it.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 112507e..97404dc 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,7 +20,6 @@
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
-import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -6252,7 +6251,6 @@
* <p>e.g. When sharing a photo, metadata could inform the user that location data is included
* in the photo they are sharing.</p>
*/
- @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA)
public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
/**
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 2e252c1..32d1964 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.PendingIntentInfo;
@@ -32,6 +33,10 @@
import android.os.UserHandle;
import android.util.AndroidException;
+import com.android.window.flags.Flags;
+
+import java.util.concurrent.Executor;
+
/**
* A description of an Intent and target action to perform with it.
* The returned object can be
@@ -114,15 +119,15 @@
implements Runnable {
private final IntentSender mIntentSender;
private final OnFinished mWho;
- private final Handler mHandler;
+ private final Executor mExecutor;
private Intent mIntent;
private int mResultCode;
private String mResultData;
private Bundle mResultExtras;
- FinishedDispatcher(IntentSender pi, OnFinished who, Handler handler) {
+ FinishedDispatcher(IntentSender pi, OnFinished who, Executor executor) {
mIntentSender = pi;
mWho = who;
- mHandler = handler;
+ mExecutor = executor;
}
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
@@ -130,10 +135,10 @@
mResultCode = resultCode;
mResultData = data;
mResultExtras = extras;
- if (mHandler == null) {
+ if (mExecutor == null) {
run();
} else {
- mHandler.post(this);
+ mExecutor.execute(this);
}
}
public void run() {
@@ -147,16 +152,16 @@
* caller to specify information about the Intent to use and be notified
* when the send has completed.
*
- * @param context The Context of the caller. This may be null if
- * <var>intent</var> is also null.
+ * @param context The Context of the caller. This may be {@code null} if
+ * <var>intent</var> is also {@code null}.
* @param code Result code to supply back to the IntentSender's target.
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
- * original Intent. Use null to not modify the original Intent.
+ * original Intent. Use {@code null} to not modify the original Intent.
* @param onFinished The object to call back on when the send has
- * completed, or null for no callback.
+ * completed, or {@code null} for no callback.
* @param handler Handler identifying the thread on which the callback
- * should happen. If null, the callback will happen from the thread
+ * should happen. If {@code null}, the callback will happen from the thread
* pool of the process.
*
*
@@ -165,8 +170,8 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null,
- SEND_INTENT_DEFAULT_OPTIONS);
+ sendIntent(context, code, intent, null, SEND_INTENT_DEFAULT_OPTIONS,
+ handler == null ? null : handler::post, onFinished);
}
/**
@@ -174,22 +179,22 @@
* caller to specify information about the Intent to use and be notified
* when the send has completed.
*
- * @param context The Context of the caller. This may be null if
- * <var>intent</var> is also null.
+ * @param context The Context of the caller. This may be {@code null} if
+ * <var>intent</var> is also {@code null}.
* @param code Result code to supply back to the IntentSender's target.
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
- * original Intent. Use null to not modify the original Intent.
+ * original Intent. Use {@code null} to not modify the original Intent.
* @param onFinished The object to call back on when the send has
- * completed, or null for no callback.
+ * completed, or {@code null} for no callback.
* @param handler Handler identifying the thread on which the callback
- * should happen. If null, the callback will happen from the thread
+ * should happen. If {@code null}, the callback will happen from the thread
* pool of the process.
* @param requiredPermission Name of permission that a recipient of the PendingIntent
* is required to hold. This is only valid for broadcast intents, and
* corresponds to the permission argument in
* {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
- * If null, no permission is required.
+ * If {@code null}, no permission is required.
*
*
* @throws SendIntentException Throws CanceledIntentException if the IntentSender
@@ -198,8 +203,8 @@
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, requiredPermission,
- SEND_INTENT_DEFAULT_OPTIONS);
+ sendIntent(context, code, intent, requiredPermission, SEND_INTENT_DEFAULT_OPTIONS,
+ handler == null ? null : handler::post, onFinished);
}
/**
@@ -207,32 +212,32 @@
* caller to specify information about the Intent to use and be notified
* when the send has completed.
*
- * @param context The Context of the caller. This may be null if
- * <var>intent</var> is also null.
+ * @param context The Context of the caller. This may be {@code null} if
+ * <var>intent</var> is also {@code null}.
* @param code Result code to supply back to the IntentSender's target.
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
- * original Intent. Use null to not modify the original Intent.
+ * original Intent. Use {@code null} to not modify the original Intent.
* @param onFinished The object to call back on when the send has
- * completed, or null for no callback.
- * @param handler Handler identifying the thread on which the callback
- * should happen. If null, the callback will happen from the thread
+ * completed, or {@code null} for no callback.
+ * @param executor Executor identifying the thread on which the callback
+ * should happen. If {@code null}, the callback will happen from the thread
* pool of the process.
* @param requiredPermission Name of permission that a recipient of the PendingIntent
* is required to hold. This is only valid for broadcast intents, and
* corresponds to the permission argument in
* {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
- * If null, no permission is required.
+ * If {@code null}, no permission is required.
* @param options Additional options the caller would like to provide to modify the sending
- * behavior. May be built from an {@link ActivityOptions} to apply to an activity start.
+ * behavior. Typically built from using {@link ActivityOptions} to apply to an activity start.
*
* @throws SendIntentException Throws CanceledIntentException if the IntentSender
* is no longer allowing more intents to be sent through it.
- * @hide
*/
- public void sendIntent(Context context, int code, Intent intent,
- OnFinished onFinished, Handler handler, String requiredPermission,
- @Nullable Bundle options)
+ @FlaggedApi(Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS)
+ public void sendIntent(@Nullable Context context, int code, @Nullable Intent intent,
+ @Nullable String requiredPermission, @Nullable Bundle options,
+ @Nullable Executor executor, @Nullable OnFinished onFinished)
throws SendIntentException {
try {
String resolvedType = intent != null ?
@@ -243,7 +248,7 @@
int res = ActivityManager.getService().sendIntentSender(app, mTarget, mWhitelistToken,
code, intent, resolvedType,
onFinished != null
- ? new FinishedDispatcher(this, onFinished, handler)
+ ? new FinishedDispatcher(this, onFinished, executor)
: null,
requiredPermission, options);
if (res < 0) {
@@ -268,7 +273,7 @@
* sending the Intent. The returned string is supplied by the system, so
* that an application can not spoof its package.
*
- * @return The package name of the PendingIntent, or null if there is
+ * @return The package name of the PendingIntent, or {@code null} if there is
* none associated with it.
*/
public String getCreatorPackage() {
@@ -296,7 +301,7 @@
* {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
* more explanation of user handles.
*
- * @return The user handle of the PendingIntent, or null if there is
+ * @return The user handle of the PendingIntent, {@code null} if there is
* none associated with it.
*/
public UserHandle getCreatorUserHandle() {
@@ -355,11 +360,11 @@
};
/**
- * Convenience function for writing either a IntentSender or null pointer to
+ * Convenience function for writing either a IntentSender or {@code null} pointer to
* a Parcel. You must use this with {@link #readIntentSenderOrNullFromParcel}
* for later reading it.
*
- * @param sender The IntentSender to write, or null.
+ * @param sender The IntentSender to write, or {@code null}.
* @param out Where to write the IntentSender.
*/
public static void writeIntentSenderOrNullToParcel(IntentSender sender,
@@ -369,13 +374,13 @@
}
/**
- * Convenience function for reading either a Messenger or null pointer from
+ * Convenience function for reading either a Messenger or {@code null} pointer from
* a Parcel. You must have previously written the Messenger with
* {@link #writeIntentSenderOrNullToParcel}.
*
* @param in The Parcel containing the written Messenger.
*
- * @return Returns the Messenger read from the Parcel, or null if null had
+ * @return Returns the Messenger read from the Parcel, or @code null} if @code null} had
* been written.
*/
public static IntentSender readIntentSenderOrNullFromParcel(Parcel in) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 57ffed4..6952a09 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -941,6 +941,8 @@
CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
CONFIG_GRAMMATICAL_GENDER,
+ CONFIG_ASSETS_PATHS,
+ CONFIG_RESOURCES_UNUSED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Config {}
@@ -1060,8 +1062,8 @@
* can itself handle asset path changes. Set from the {@link android.R.attr#configChanges}
* attribute. This is not a core resource configuration, but a higher-level value, so its
* constant starts at the high bits.
- * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs.
*/
+ @FlaggedApi(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
public static final int CONFIG_ASSETS_PATHS = 0x80000000;
/**
* Bit in {@link #configChanges} that indicates that the activity
@@ -1088,6 +1090,30 @@
*/
public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
+ /**
+ * <p>This is probably not the constant you want, the resources compiler supports a less
+ * dangerous version of it, 'allKnown', that only suppresses all currently existing
+ * configuration change restarts depending on your target SDK rather than whatever the latest
+ * SDK supports, allowing the application to work with resources on future Platform versions.
+ *
+ * <p>Bit in {@link #configChanges} that indicates that the activity doesn't use Android
+ * Resources at all and doesn't need to be restarted on any configuration changes. This bit
+ * disables all restarts for configuration dimensions available in the current target SDK as
+ * well as dimensions introduced in future SDKs. Use it only if the activity doesn't need
+ * anything from its resources, and doesn't depend on any libraries that may provide resources
+ * and need to respond to configuration changes. When set,
+ * {@link Activity#onConfigurationChanged(Configuration)} will be called instead of a restart,
+ * and it’s up to the implementation to ensure that no stale resource values remain loaded
+ * anywhere in the code.
+ *
+ * <p>This overrides all other bits, and this is recommended to be used individually.
+ *
+ * <p>This is not a core resource configuration, but a higher-level value, so its constant
+ * starts at the high bits.
+ */
+ @FlaggedApi(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+ public static final int CONFIG_RESOURCES_UNUSED = 0x8000000;
+
/** @hide
* Unfortunately the constants for config changes in native code are
* different from ActivityInfo. :( Here are the values we should use for the
@@ -1657,7 +1683,8 @@
* {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
* {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
* {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION},
- * {@link #CONFIG_COLOR_MODE}, and {link #CONFIG_GRAMMATICAL_GENDER}.
+ * {@link #CONFIG_COLOR_MODE}, {@link #CONFIG_GRAMMATICAL_GENDER},
+ * {@link #CONFIG_ASSETS_PATHS}, and {@link #CONFIG_RESOURCES_UNUSED}.
* Set from the {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7d94c6..26ee4e8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -161,6 +161,16 @@
}
flag {
+ name: "cache_profile_parent"
+ namespace: "multiuser"
+ description: "Cache getProfileParent to avoid unnecessary binder calls"
+ bug: "350417399"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "cache_quiet_mode_state"
namespace: "multiuser"
description: "Optimise quiet mode state retrieval"
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a475cc8..a5f8199 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -56,3 +56,13 @@
# This flag is read in ResourcesImpl at boot time.
is_fixed_read_only: true
}
+
+flag {
+ name: "handle_all_config_changes"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for allowing activities to handle all kinds of configuration changes"
+ bug: "180625460"
+ # This flag is read at boot time.
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 6201359..6a7ee7f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -56,6 +56,8 @@
import android.hardware.camera2.utils.ExceptionUtils;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.feature.flags.FeatureFlags;
+import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Handler;
@@ -247,14 +249,22 @@
private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners =
new ArrayList<>();
private boolean mFoldedDeviceState;
+ private final FeatureFlags mDeviceStateManagerFlags;
public FoldStateListener(Context context) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
+ mDeviceStateManagerFlags = new FeatureFlagsImpl();
}
- private synchronized void handleStateChange(int state) {
- boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+ private synchronized void handleStateChange(DeviceState state) {
+ final boolean folded;
+ if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ folded = state.hasProperty(
+ DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ } else {
+ folded = ArrayUtils.contains(mFoldedDeviceStates, state.getIdentifier());
+ }
mFoldedDeviceState = folded;
Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator();
@@ -276,10 +286,8 @@
@SuppressWarnings("FlaggedApi")
@Override
- public void onDeviceStateChanged(DeviceState state) {
- // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to
- // system API which requires it to be flagged.
- handleStateChange(state.getIdentifier());
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
+ handleStateChange(state);
}
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index febc24c1..5b511cc 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -67,6 +67,14 @@
public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000;
/**
+ * {@link DeviceState} to represent an invalid device state.
+ * @hide
+ */
+ public static final DeviceState INVALID_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+ "INVALID").build());
+
+ /**
* Intent needed to launch the rear display overlay activity from SysUI
*
* @hide
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4894fb1..97f6899 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -395,7 +395,7 @@
* the display is removed.
*
* Public virtual displays without this flag will move their content to main display
- * stack once they're removed. Private vistual displays will always destroy their
+ * stack once they're removed. Private virtual displays will always destroy their
* content on removal even without this flag.
*
* @see #createVirtualDisplay
@@ -1669,6 +1669,46 @@
}
/**
+ * Gets the mapping between the doze brightness sensor values and brightness values. The doze
+ * brightness sensor is a light sensor used to determine the brightness while the device is
+ * dozing. Light sensor values are typically integers in the rage of 0-4. The returned values
+ * are between {@link PowerManager#BRIGHTNESS_MIN} and {@link PowerManager#BRIGHTNESS_MAX}, or
+ * -1 meaning that the current brightness should be kept.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ return mGlobal.getDozeBrightnessSensorValueToBrightness(displayId);
+ }
+
+ /**
+ * Gets the default doze brightness.
+ * The returned values are between {@link PowerManager#BRIGHTNESS_MIN} and
+ * {@link PowerManager#BRIGHTNESS_MAX}.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ return mGlobal.getDefaultDozeBrightness(displayId);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e9cd37a..cae33d0 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -21,6 +21,7 @@
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1226,6 +1227,32 @@
}
}
+ /**
+ * @see DisplayManager#getDozeBrightnessSensorValueToBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ try {
+ return mDm.getDozeBrightnessSensorValueToBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see DisplayManager#getDefaultDozeBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ try {
+ return mDm.getDefaultDozeBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 91caedc..811a834 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -686,8 +686,12 @@
public RefreshRateRange range;
public RefreshRateLimitation(@RefreshRateLimitType int type, float min, float max) {
+ this(type, new RefreshRateRange(min, max));
+ }
+
+ public RefreshRateLimitation(@RefreshRateLimitType int type, RefreshRateRange range) {
this.type = type;
- range = new RefreshRateRange(min, max);
+ this.range = range;
}
@Override
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 77277ee..f3c21e9f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -246,4 +246,12 @@
// Restricts display modes to specified modeIds.
@EnforcePermission("RESTRICT_DISPLAY_MODES")
void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
+
+ // Get the mapping between the doze brightness sensor values and brightness values
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float[] getDozeBrightnessSensorValueToBrightness(int displayId);
+
+ // Get the default doze brightness
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float getDefaultDozeBrightness(int displayId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 23e262c..d7952eb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1251,8 +1251,22 @@
* canceled. Only the pilfering window will continue to receive events for the affected pointers
* until the pointer is lifted.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * Furthermore, if any new pointers go down within the touchable region of the pilfering window
+ * and are part of the same gesture, those new pointers will be pilfered as well, and will not
+ * be sent to any other windows.
+ *
+ * Pilfering is designed to be used only once per gesture. Once the gesture is complete
+ * (i.e. on {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_CANCEL},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}), the system will resume dispatching pointers
+ * to the appropriately touched windows.
+ *
+ * NOTE: This method should be used with caution as unexpected pilfering can break fundamental
+ * user interactions.
+ *
+ * NOTE: Since this method pilfers pointers based on gesture stream that is
+ * currently active for the window, the behavior will depend on the state of the system, and
+ * is inherently racy. For example, a pilfer request on a quick tap may not be successful if
+ * the tap is already complete by the time the pilfer request is received by the system.
*
* @see android.os.InputConfig#SPY
* @hide
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index 3be911abe7..8c98abd 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -107,7 +107,8 @@
}
/**
- * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive.
+ * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. By default, the scroll
+ * amount is 0, which results in no scroll.
* <p>
* Positive values indicate scrolling forward (e.g. down in a vertical list); negative
* values, backward.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 16d9ef2..0cd2800 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -68,3 +68,10 @@
description: "Controls if the mouse keys accessibility feature for physical keyboard is available to the user"
bug: "341799888"
}
+
+flag {
+ namespace: "input_native"
+ name: "touchpad_visualizer"
+ description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
+ bug: "286551975"
+}
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index e2d215e..4bc5bd2 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -29,6 +29,7 @@
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.window.WindowProviderService;
@@ -186,6 +187,10 @@
if (callback != null) {
callback.finishedEvent(seq, handled);
}
+ if (Flags.imeSwitcherRevamp() && !handled && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getUnicodeChar() > 0 && mInputMethodServiceInternal != null) {
+ mInputMethodServiceInternal.notifyUserActionIfNecessary();
+ }
}
/**
diff --git a/core/java/android/inputmethodservice/ImsConfigurationTracker.java b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
index 30ef0a2..17a5ffc 100644
--- a/core/java/android/inputmethodservice/ImsConfigurationTracker.java
+++ b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
@@ -16,6 +16,8 @@
package android.inputmethodservice;
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
+
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -88,12 +90,16 @@
if (!mInitialized) {
return;
}
+ // Don't restart InputMethodService that claims it does not use resources at all.
+ boolean neverReset = android.content.res.Flags.handleAllConfigChanges()
+ && (mHandledConfigChanges & CONFIG_RESOURCES_UNUSED) != 0;
+
final int diff = mLastKnownConfig != null
? mLastKnownConfig.diffPublicOnly(newConfig) : CONFIG_CHANGED;
// If the new config is the same as the config this Service is already running with,
// then don't bother calling resetStateForNewConfiguration.
final int unhandledDiff = (diff & ~mHandledConfigChanges);
- if (unhandledDiff != 0) {
+ if (unhandledDiff != 0 && !neverReset) {
resetStateForNewConfigurationRunner.run();
}
if (diff != 0) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 943b04f..d560399 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -101,6 +101,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.InputType;
import android.text.Layout;
@@ -4341,6 +4342,16 @@
}
/**
+ * Called when the IME switch button was clicked from the client. This will show the input
+ * method picker dialog.
+ *
+ * @hide
+ */
+ final void onImeSwitchButtonClickFromClient() {
+ mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId(), UserHandle.myUserId());
+ }
+
+ /**
* Used to inject custom {@link InputMethodServiceInternal}.
*
* @return the {@link InputMethodServiceInternal} to be used.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index de67e06..3ce67b0 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -42,6 +42,7 @@
import android.view.WindowInsetsController.Appearance;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -145,7 +146,8 @@
return mImpl.toDebugString();
}
- private static final class Impl implements Callback, Window.DecorCallback {
+ private static final class Impl implements Callback, Window.DecorCallback,
+ NavigationBarView.ButtonClickListener {
private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
// Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
@@ -241,6 +243,7 @@
? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN
: 0);
navigationBarView.setNavigationIconHints(hints);
+ navigationBarView.prepareNavButtons(this);
}
} else {
mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
@@ -592,6 +595,17 @@
return drawLegacyNavigationBarBackground;
}
+ @Override
+ public void onImeSwitchButtonClick(View v) {
+ mService.onImeSwitchButtonClickFromClient();
+ }
+
+ @Override
+ public boolean onImeSwitchButtonLongClick(View v) {
+ v.getContext().getSystemService(InputMethodManager.class).showInputMethodPicker();
+ return true;
+ }
+
/**
* Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
*/
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index f423672..540243c 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -41,6 +41,7 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
@@ -58,12 +59,30 @@
private int mTouchDownY;
private AudioManager mAudioManager;
private boolean mGestureAborted;
+ /**
+ * Whether the long click action has been invoked. The short click action is invoked on the up
+ * event while a long click is invoked as soon as the long press duration is reached, so a long
+ * click could be performed before the short click is checked, in which case the short click's
+ * action should not be invoked.
+ *
+ * @see View#mHasPerformedLongPress
+ */
+ private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ /** Runnable for checking whether the long click action should be performed. */
+ private final Runnable mCheckLongPress = new Runnable() {
+ public void run() {
+ if (isPressed() && performLongClick()) {
+ mLongClicked = true;
+ }
+ }
+ };
+
public KeyButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -159,6 +178,7 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
+ mLongClicked = false;
setPressed(true);
// Use raw X and Y to detect gestures in case a parent changes the x and y values
@@ -173,6 +193,10 @@
if (!showSwipeUI) {
playSoundEffect(SoundEffectConstants.CLICK);
}
+ if (Flags.imeSwitcherRevamp() && isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+ }
break;
case MotionEvent.ACTION_MOVE:
x = (int) ev.getRawX();
@@ -183,6 +207,9 @@
// When quick step is enabled, prevent animating the ripple triggered by
// setPressed and decide to run it on touch up
setPressed(false);
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
}
break;
case MotionEvent.ACTION_CANCEL:
@@ -190,9 +217,12 @@
if (mCode != KEYCODE_UNKNOWN) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_UP:
- final boolean doIt = isPressed();
+ final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
if (showSwipeUI) {
@@ -201,7 +231,7 @@
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
playSoundEffect(SoundEffectConstants.CLICK);
}
- } else if (doHapticFeedback) {
+ } else if (doHapticFeedback && !mLongClicked) {
// Always send a release ourselves because it doesn't seem to be sent elsewhere
// and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
@@ -221,6 +251,9 @@
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
}
+ if (isLongClickable()) {
+ removeCallbacks(mCheckLongPress);
+ }
break;
}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
index 4bb66ed..93c5439 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -17,7 +17,6 @@
package android.inputmethodservice.navigationbar;
import android.annotation.ColorInt;
-import android.graphics.Color;
final class NavigationBarConstants {
private NavigationBarConstants() {
@@ -28,13 +27,13 @@
// TODO(b/215443343): Handle this in the drawable then remove this constant.
static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
- // Copied from "white" at packages/SettingsLib/res/values/colors.xml
+ // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
@ColorInt
- static final int WHITE = Color.WHITE;
+ static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
- // Copied from "black" at packages/SettingsLib/res/values/colors.xml
+ // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
@ColorInt
- static final int BLACK = Color.BLACK;
+ static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
// Copied from "navigation_bar_deadzone_hold"
static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index b522e9b..a3beaf4 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -16,8 +16,8 @@
package android.inputmethodservice.navigationbar;
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.BLACK;
-import static android.inputmethodservice.navigationbar.NavigationBarConstants.WHITE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -26,6 +26,7 @@
import android.animation.PropertyValuesHolder;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -39,6 +40,7 @@
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -79,12 +81,34 @@
private NavigationBarInflaterView mNavigationInflaterView;
+ /**
+ * Interface definition for callbacks to be invoked when navigation bar buttons are clicked.
+ */
+ public interface ButtonClickListener {
+
+ /**
+ * Called when the IME switch button is clicked.
+ *
+ * @param v The view that was clicked.
+ */
+ void onImeSwitchButtonClick(View v);
+
+ /**
+ * Called when the IME switch button has been clicked and held.
+ *
+ * @param v The view that was clicked and held.
+ *
+ * @return true if the callback consumed the long click, false otherwise.
+ */
+ boolean onImeSwitchButtonLongClick(View v);
+ }
+
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mLightContext = context;
- mLightIconColor = WHITE;
- mDarkIconColor = BLACK;
+ mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+ mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
@@ -98,13 +122,27 @@
new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
+ }
+ /**
+ * Prepares the navigation bar buttons to be used and sets the on click listeners.
+ *
+ * @param listener The listener used to handle the clicks on the navigation bar buttons.
+ */
+ public void prepareNavButtons(@NonNull ButtonClickListener listener) {
getBackButton().setLongClickable(false);
- final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
- imeSwitchButton.setLongClickable(false);
- imeSwitchButton.setOnClickListener(view -> view.getContext()
- .getSystemService(InputMethodManager.class).showInputMethodPicker());
+ if (Flags.imeSwitcherRevamp()) {
+ final var imeSwitchButton = getImeSwitchButton();
+ imeSwitchButton.setLongClickable(true);
+ imeSwitchButton.setOnClickListener(listener::onImeSwitchButtonClick);
+ imeSwitchButton.setOnLongClickListener(listener::onImeSwitchButtonLongClick);
+ } else {
+ final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
+ imeSwitchButton.setLongClickable(false);
+ imeSwitchButton.setOnClickListener(view -> view.getContext()
+ .getSystemService(InputMethodManager.class).showInputMethodPicker());
+ }
}
@Override
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 406a1a6..026013c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -564,8 +564,7 @@
BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
- BRIGHTNESS_CONSTRAINT_TYPE_DIM,
- BRIGHTNESS_CONSTRAINT_TYPE_DOZE
+ BRIGHTNESS_CONSTRAINT_TYPE_DIM
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessConstraint{}
@@ -594,12 +593,6 @@
public static final int BRIGHTNESS_CONSTRAINT_TYPE_DIM = 3;
/**
- * Brightness constraint type: minimum allowed value.
- * @hide
- */
- public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
-
- /**
* @hide
*/
@IntDef(prefix = { "WAKE_REASON_" }, value = {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f30a9f5..3aa42c6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3927,7 +3927,12 @@
android.Manifest.permission.QUERY_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
- return mUserPropertiesCache.query(userHandle.getIdentifier());
+ final int userId = userHandle.getIdentifier();
+ // Avoid calling into system server for invalid user ids.
+ if (android.multiuser.Flags.fixGetUserPropertyCache() && userId < 0) {
+ throw new IllegalArgumentException("Cannot access properties for user " + userId);
+ }
+ return mUserPropertiesCache.query(userId);
}
/**
@@ -5663,6 +5668,33 @@
}
}
+ private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
+
+ private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer query) {
+ try {
+ return mService.isQuietModeEnabled(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+
+ /** @hide */
+ public static final void invalidateQuietModeEnabledCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+ }
+
/**
* Returns whether the given profile is in quiet mode or not.
*
@@ -5670,6 +5702,13 @@
* @return true if the profile is in quiet mode, false otherwise.
*/
public boolean isQuietModeEnabled(UserHandle userHandle) {
+ if (android.multiuser.Flags.cacheQuietModeState()){
+ final int userId = userHandle.getIdentifier();
+ if (userId < 0) {
+ return false;
+ }
+ return mQuietModeEnabledCache.query(userId);
+ }
try {
return mService.isQuietModeEnabled(userHandle.getIdentifier());
} catch (RemoteException re) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index c73a422..ad2f59d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -46,6 +46,7 @@
flag {
namespace: "haptics"
name: "vibration_xml_apis"
+ is_exported: true
description: "Enabled System APIs for vibration effect XML parser and serializer"
bug: "347273158"
metadata {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3954bc2..f6f0eff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -92,3 +92,10 @@
description: "Add a dump capability for attestation_verification service"
bug: "335498868"
}
+
+flag {
+ name: "should_trust_manager_listen_for_primary_auth"
+ namespace: "biometrics"
+ description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
+ bug: "323086607"
+}
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 82c1613..1f1a351 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -2,14 +2,6 @@
container: "system"
flag {
- name: "enable_sharesheet_metadata_extra"
- is_exported: true
- namespace: "intentresolver"
- description: "This flag enables sharesheet metadata to be displayed to users."
- bug: "318942069"
-}
-
-flag {
name: "chooser_payload_toggling"
is_exported: true
namespace: "intentresolver"
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index be60c25..04dba46 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -46,3 +46,11 @@
is_fixed_read_only: true
bug: "323165543"
}
+
+flag {
+ name: "client_side_proto_logging"
+ namespace: "windowing_tools"
+ description: "Add support for client side protologging"
+ is_fixed_read_only: true
+ bug: "352538294"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
"settings_show_stylus_preferences";
/**
- * Flag to enable/disable biometrics enrollment v2
- * @hide
- */
- public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
- /**
* Flag to enable/disable FingerprintSettings v2
* @hide
*/
@@ -223,7 +217,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
- DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index cb5a885..e5be531 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,6 +53,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
+import android.view.KeyboardShortcutGroup;
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
@@ -1095,4 +1096,11 @@
boolean transferTouchGesture(in InputTransferToken transferFromToken,
in InputTransferToken transferToToken);
+
+ /**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
}
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 2302dc7..ea4abc1 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -50,13 +50,12 @@
private final SurfaceControl mSurface;
/**
- * Takes all of the current pointer events streams that are currently being sent to this
- * monitor and generates appropriate cancellations for the windows that would normally get
- * them.
+ * Pilfer pointers from this input monitor.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * @see android.hardware.input.InputManager#pilferPointers(IBinder)
+ * @deprecated
*/
+ @Deprecated
public void pilferPointers() {
try {
mHost.pilferPointers();
@@ -197,10 +196,10 @@
};
@DataClass.Generated(
- time = 1679692514588L,
+ time = 1720819824835L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic @java.lang.Deprecated void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index fe98fab..075571c 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -162,6 +162,12 @@
return mSource;
}
+ /** Set the flags of this provider. */
+ public InsetsFrameProvider setFlags(@Flags int flags) {
+ mFlags = flags;
+ return this;
+ }
+
public InsetsFrameProvider setFlags(@Flags int flags, @Flags int mask) {
mFlags = (mFlags & ~mask) | (flags & mask);
return this;
diff --git a/core/java/android/view/KeyboardShortcutGroup.aidl b/core/java/android/view/KeyboardShortcutGroup.aidl
new file mode 100644
index 0000000..6f219db
--- /dev/null
+++ b/core/java/android/view/KeyboardShortcutGroup.aidl
@@ -0,0 +1,3 @@
+package android.view;
+
+@JavaOnlyStableParcelable parcelable KeyboardShortcutGroup;
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 3f6fd64..3f49bf3 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -81,12 +81,29 @@
* {@link KeyEvent#META_SYM_ON}.
*/
public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) {
+ this(label, null, baseCharacter, modifiers);
+ }
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param icon An icon that identifies the action performed by this shortcut.
+ * @param baseCharacter The character that triggers the shortcut.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ *
+ * @hide
+ */
+ public KeyboardShortcutInfo(
+ CharSequence label, @Nullable Icon icon, char baseCharacter, int modifiers) {
mLabel = label;
checkArgument(baseCharacter != MIN_VALUE);
mBaseCharacter = baseCharacter;
mKeycode = KeyEvent.KEYCODE_UNKNOWN;
mModifiers = modifiers;
- mIcon = null;
+ mIcon = icon;
}
private KeyboardShortcutInfo(Parcel source) {
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 117b200..a806bd2 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -295,16 +295,7 @@
}
sCallStackDebuggingInitialized = true;
- sCallStackDebuggingMatchCall =
- SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
- .toLowerCase();
- sCallStackDebuggingMatchName =
- SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
- .toLowerCase();
- sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply");
- // Only enable stack debugging if any of the match filters are set
- sCallStackDebuggingEnabled = !sCallStackDebuggingMatchCall.isEmpty()
- || !sCallStackDebuggingMatchName.isEmpty();
+ updateCallStackDebuggingParams();
if (sCallStackDebuggingEnabled) {
Log.d(TAG, "Enabling transaction call stack debugging:"
+ " matchCall=" + sCallStackDebuggingMatchCall
@@ -325,6 +316,11 @@
final void checkCallStackDebugging(@NonNull String call,
@Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
@Nullable String details) {
+
+ if (sCallStackDebuggingInitialized && sCallStackDebuggingEnabled) {
+ updateCallStackDebuggingParams();
+ }
+
if (!sCallStackDebuggingEnabled) {
return;
}
@@ -356,6 +352,22 @@
}
/**
+ * Updates the call stack debugging params from the system properties.
+ */
+ private static void updateCallStackDebuggingParams() {
+ sCallStackDebuggingMatchCall =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+ .toLowerCase();
+ sCallStackDebuggingMatchName =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+ .toLowerCase();
+ sLogAllTxCallsOnApply = sCallStackDebuggingMatchCall.contains("apply");
+ // Only enable stack debugging if any of the match filters are set
+ sCallStackDebuggingEnabled = !sCallStackDebuggingMatchCall.isEmpty()
+ || !sCallStackDebuggingMatchName.isEmpty();
+ }
+
+ /**
* Tests whether the given surface control name/method call matches the filters set for the
* call stack debugging.
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f77e219..88d7a83 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22,6 +22,7 @@
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -19638,7 +19639,6 @@
*/
public final void setLeft(int left) {
if (left != mLeft) {
- mPrivateFlags4 |= PFLAG4_HAS_MOVED;
final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
if (mAttachInfo != null) {
@@ -25592,6 +25592,7 @@
? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_LARGE;
}
+ mPrivateFlags4 |= PFLAG4_HAS_MOVED;
}
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
@@ -33905,8 +33906,24 @@
* @hide
*/
protected int calculateFrameRateCategory() {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ ViewParent parent = mParent;
+ boolean isInputMethodWindowType =
+ viewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+
+ // boost frame rate when the position or the size changed.
+ if (((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
+ PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
+ || mLastFrameTop != mTop)
+ && viewRootImpl.shouldCheckFrameRateCategory()
+ && parent instanceof View
+ && ((View) parent).mFrameContentVelocity <= 0
+ && !isInputMethodWindowType) {
+
+ return FRAME_RATE_CATEGORY_HIGH_HINT | FRAME_RATE_CATEGORY_REASON_BOOST;
+ }
int category;
- switch (getViewRootImpl().intermittentUpdateState()) {
+ switch (viewRootImpl.intermittentUpdateState()) {
case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> {
if (!sToolkitFrameRateBySizeReadOnlyFlagValue) {
category = FRAME_RATE_CATEGORY_NORMAL;
@@ -33940,66 +33957,23 @@
}
float velocity = mFrameContentVelocity;
final float frameRate = mPreferredFrameRate;
- ViewParent parent = mParent;
- boolean isInputMethodWindowType = false;
- if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
- isInputMethodWindowType =
- mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
- }
- if (velocity <= 0 && Float.isNaN(frameRate)) {
- // The most common case is when nothing is set, so this special case is called
- // often.
- if (mAttachInfo.mViewVelocityApi
- && ((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
- PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
- || mLastFrameTop != mTop)
- && viewRootImpl.shouldCheckFrameRate(false)
- && parent instanceof View
- && ((View) parent).mFrameContentVelocity <= 0
- && !isInputMethodWindowType) {
- viewRootImpl.votePreferredFrameRate(MAX_FRAME_RATE, FRAME_RATE_COMPATIBILITY_GTE);
- }
- if (viewRootImpl.shouldCheckFrameRateCategory()) {
- int frameRateCategory = calculateFrameRateCategory();
- int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
- int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
- viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
- mLastFrameRateCategory = frameRateCategory;
- }
- mLastFrameLeft = mLeft;
- mLastFrameTop = mTop;
- return;
- }
- if (viewRootImpl.shouldCheckFrameRate(frameRate > 0f)) {
+
+ if (viewRootImpl.shouldCheckFrameRate(frameRate > 0f)
+ && (frameRate > 0 || (mAttachInfo.mViewVelocityApi && velocity > 0f))) {
float velocityFrameRate = 0f;
- if (mAttachInfo.mViewVelocityApi) {
- if (velocity < 0f
- && ((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
- PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
- || mLastFrameTop != mTop)
- && mParent instanceof View
- && ((View) mParent).mFrameContentVelocity <= 0
- && !isInputMethodWindowType
- ) {
- // This current calculation is very simple. If something on the screen
- // moved, then it votes for the highest velocity.
- velocityFrameRate = MAX_FRAME_RATE;
- } else if (velocity > 0f) {
- velocityFrameRate = convertVelocityToFrameRate(velocity);
- }
+ if (mAttachInfo.mViewVelocityApi && velocity > 0f) {
+ velocityFrameRate = convertVelocityToFrameRate(velocity);
}
- if (velocityFrameRate > 0f || frameRate > 0f) {
- int compatibility;
- float frameRateToSet;
- if (frameRate >= velocityFrameRate) {
- compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- frameRateToSet = frameRate;
- } else {
- compatibility = FRAME_RATE_COMPATIBILITY_GTE;
- frameRateToSet = velocityFrameRate;
- }
- viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility);
+ int compatibility;
+ float frameRateToSet;
+ if (frameRate >= velocityFrameRate) {
+ compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ frameRateToSet = frameRate;
+ } else {
+ compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+ frameRateToSet = velocityFrameRate;
}
+ viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility);
}
if (viewRootImpl.shouldCheckFrameRateCategory()) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 07cbaa9..e5b17c8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -128,8 +128,8 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
+import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption;
import static com.android.window.flags.Flags.insetsControlChangedItem;
import static com.android.window.flags.Flags.insetsControlSeq;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -1373,9 +1373,6 @@
*/
public void setActivityConfigCallback(@Nullable ActivityConfigCallback callback) {
mActivityConfigCallback = callback;
- if (!activityWindowInfoFlag()) {
- return;
- }
if (callback == null) {
mPendingActivityWindowInfo = null;
mLastReportedActivityWindowInfo = null;
@@ -3189,7 +3186,9 @@
inOutParams.privateFlags &= ~PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
}
- private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
+ /** Updates the {@link InsetsType}s to hide or show based on layout params. */
+ @VisibleForTesting
+ public void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
final int flags = params.flags;
final boolean matchParent = params.width == MATCH_PARENT && params.height == MATCH_PARENT;
@@ -3200,6 +3199,9 @@
|| ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
final boolean navWasHiddenByFlags = (mTypesHiddenByFlags & Type.navigationBars()) != 0;
final boolean navIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+ final boolean captionWasHiddenByFlags = (mTypesHiddenByFlags & Type.captionBar()) != 0;
+ final boolean captionIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+ || ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
@InsetsType int typesToHide = 0;
@InsetsType int typesToShow = 0;
@@ -3213,6 +3215,13 @@
} else if (!navIsHiddenByFlags && navWasHiddenByFlags) {
typesToShow |= Type.navigationBars();
}
+ if (captionIsHiddenByFlags && !captionWasHiddenByFlags
+ && enableCaptionCompatInsetForceConsumption()) {
+ typesToHide |= Type.captionBar();
+ } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags
+ && enableCaptionCompatInsetForceConsumption()) {
+ typesToShow |= Type.captionBar();
+ }
if (typesToHide != 0) {
getInsetsController().hide(typesToHide);
}
@@ -4424,6 +4433,8 @@
}
mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
+ mFrameRateCategoryHighHintCount = mFrameRateCategoryHighHintCount > 0
+ ? mFrameRateCategoryHighHintCount - 1 : mFrameRateCategoryHighHintCount;
mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
@@ -9345,7 +9356,7 @@
onClientWindowFramesChanged(mTmpFrames);
- if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
+ if (mPendingActivityWindowInfo != null) {
final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo;
if (outInfo != null) {
mPendingActivityWindowInfo.set(outInfo);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 18006bb..14978ed 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1668,6 +1668,15 @@
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
/**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ *
+ * @hide
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Request for ime's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b667427..330e46a 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -237,6 +237,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ try {
+ return WindowManagerGlobal.getWindowManagerService()
+ .getApplicationLaunchKeyboardShortcuts(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public void requestImeKeyboardShortcuts(
final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index e7a2fb9..07a9794 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -465,6 +465,20 @@
}
@AnyThread
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ static void onImeSwitchButtonClickFromSystem(int displayId) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onImeSwitchButtonClickFromSystem(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
@Nullable
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 11ee286..098f655 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -92,7 +92,7 @@
*
* @see #createImeLanguageSettingsActivityIntent()
*/
- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+ @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
public static final String ACTION_IME_LANGUAGE_SETTINGS =
"android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
@@ -298,7 +298,7 @@
com.android.internal.R.styleable.InputMethod);
settingsActivityComponent = sa.getString(
com.android.internal.R.styleable.InputMethod_settingsActivity);
- if (Flags.imeSwitcherRevamp()) {
+ if (Flags.imeSwitcherRevampApi()) {
languageSettingsActivityComponent = sa.getString(
com.android.internal.R.styleable.InputMethod_languageSettingsActivity);
}
@@ -888,7 +888,7 @@
*
* @attr ref R.styleable#InputMethod_languageSettingsActivity
*/
- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+ @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
@Nullable
public Intent createImeLanguageSettingsActivityIntent() {
if (TextUtils.isEmpty(mLanguageSettingsActivityName)) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c63e58..dbbfff0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4354,6 +4354,19 @@
}
/**
+ * Called when the IME switch button was clicked from the system. This will show the input
+ * method picker dialog.
+ *
+ * @param displayId The ID of the display where the input method picker dialog should be shown.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ IInputMethodManagerGlobalInvoker.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
+ /**
* A test API for CTS to check whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 56e5bcf..e294ee2 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -82,6 +82,15 @@
}
flag {
+ name: "ime_switcher_revamp_api"
+ is_exported: true
+ namespace: "input_method"
+ description: "Feature flag for APIs for revamping the Input Method Switcher menu"
+ bug: "311791923"
+ is_fixed_read_only: true
+}
+
+flag {
name: "initiation_without_input_connection"
namespace: "input_method"
description: "Feature flag for initiating handwriting without InputConnection"
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 1034479..c53a0e1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -36,6 +36,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
@@ -339,10 +340,10 @@
if (sProviderInstance != null) return sProviderInstance;
sTimestamps.mWebViewLoadStart = SystemClock.uptimeMillis();
- final int uid = android.os.Process.myUid();
- if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
- || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
- || uid == android.os.Process.BLUETOOTH_UID) {
+ final int appId = UserHandle.getAppId(android.os.Process.myUid());
+ if (appId == android.os.Process.ROOT_UID || appId == android.os.Process.SYSTEM_UID
+ || appId == android.os.Process.PHONE_UID || appId == android.os.Process.NFC_UID
+ || appId == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
index 946bb82..71c500c 100644
--- a/core/java/android/window/ActivityWindowInfo.java
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -144,4 +146,15 @@
+ ", taskFragmentBounds=" + mTaskFragmentBounds
+ "}";
}
+
+ /** Gets the {@link ActivityWindowInfo} of the given activity. */
+ @Nullable
+ public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ return null;
+ }
+ final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ return record != null ? record.getActivityWindowInfo() : null;
+ }
}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index d28500c..12d4ab8 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,12 +16,15 @@
package android.window;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.FloatProperty;
import android.util.TimeUtils;
import android.view.Choreographer;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.dynamicanimation.animation.FlingAnimation;
import com.android.internal.dynamicanimation.animation.FloatValueHolder;
@@ -210,7 +213,8 @@
}
/** Returns true if the back animation is in progress. */
- boolean isBackAnimationInProgress() {
+ @VisibleForTesting(visibility = PACKAGE)
+ public boolean isBackAnimationInProgress() {
return mBackAnimationInProgress;
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 4706dfd..ac57c00 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,25 +39,6 @@
void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
/**
- * Registers remote animations per transition type for the organizer. It will override the
- * animations if the transition only contains windows that belong to the organized
- * TaskFragments in the given Task.
- */
- void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
- in RemoteAnimationDefinition definition);
-
- /**
- * Unregisters remote animations per transition type for the organizer.
- */
- void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
-
- /**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
- * only occupies a portion of Task bounds.
- */
- boolean isActivityEmbedded(in IBinder activityToken);
-
- /**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 461eab6..8e429cb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import android.annotation.CallSuper;
import android.annotation.FlaggedApi;
@@ -29,16 +30,17 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -205,34 +207,6 @@
}
/**
- * Registers remote animations per transition type for the organizer. It will override the
- * animations if the transition only contains windows that belong to the organized
- * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
- * @hide
- */
- @CallSuper
- public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
- try {
- getController().registerRemoteAnimations(mInterface, definition);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unregisters remote animations per transition type for the organizer.
- * @hide
- */
- @CallSuper
- public void unregisterRemoteAnimations() {
- try {
- getController().unregisterRemoteAnimations(mInterface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*
@@ -353,15 +327,15 @@
}
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and
* only occupies a portion of Task bounds.
+ *
+ * @see ActivityWindowInfo for additional window info.
* @hide
*/
- public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
- try {
- return getController().isActivityEmbedded(activityToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public static boolean isActivityEmbedded(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8ded608..1083f64 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -205,7 +205,7 @@
FLAG_SYNC,
FLAG_CONFIG_AT_END,
FLAG_FIRST_CUSTOM
- })
+ }, flag = true)
public @interface ChangeFlags {}
private final @TransitionType int mType;
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index cc22576..253337b 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -471,7 +471,7 @@
"pipTask = " + mPipTask + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
"displayChange = " + mDisplayChange + ", " +
- "flags = " + mFlags + ", " +
+ "flags = " + Integer.toHexString(mFlags) + ", " +
"debugId = " + mDebugId +
" }";
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index cd033ca..e9d77f8 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -42,6 +42,7 @@
import android.os.Parcelable;
import android.util.ArrayMap;
import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
import android.view.SurfaceControl;
import android.view.WindowInsets.Type.InsetsType;
@@ -714,14 +715,16 @@
@NonNull
public WindowContainerTransaction addInsetsSource(
@NonNull WindowContainerToken receiver,
- IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects) {
+ IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects,
+ @InsetsSource.Flags int flags) {
final HierarchyOp hierarchyOp =
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
.setContainer(receiver.asBinder())
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
.setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
.setArbitraryRectangle(frame)
- .setBoundingRects(boundingRects))
+ .setBoundingRects(boundingRects)
+ .setFlags(flags))
.setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 9b87e23..7bbc3db 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -244,6 +244,7 @@
// We should call onBackCancelled() when an active callback is removed from
// dispatcher.
sendCancelledIfInProgress(callback);
+ mHandler.post(mProgressAnimator::reset);
setTopOnBackInvokedCallback(getTopCallback());
}
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cc880e1..48fb2b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -91,6 +91,13 @@
}
flag {
+ name: "camera_compat_fullscreen_pick_same_task_activity"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Limit undo of camera compat treatment to the same task that started the treatment."
+ bug: "350495350"
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 245c0e7..3f1c06a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -40,8 +40,8 @@
flag {
name: "enable_windowing_edge_drag_resize"
namespace: "lse_desktop_experience"
- description: "Enables edge drag resizing for all input sources"
- bug: "323383067"
+ description: "Enables edge drag resizing for stylus input (cursor is enabled by default)"
+ bug: "337782092"
}
flag {
@@ -150,8 +150,36 @@
}
flag {
+ name: "enable_caption_compat_inset_conversion"
+ namespace: "lse_desktop_experience"
+ description: "Enables compatibility mitigation for apps that don't support caption insets well"
+ bug: "316231589"
+}
+
+flag {
name: "enable_desktop_windowing_multi_instance_features"
namespace: "lse_desktop_experience"
description: "Whether to enable multi-instance support in desktop windowing."
bug: "336289597"
}
+
+flag {
+ name: "enable_desktop_windowing_back_navigation"
+ namespace: "lse_desktop_experience"
+ description: "Whether to enable back navigation treatment in desktop windowing."
+ bug: "350421096"
+}
+
+flag {
+ name: "enable_desktop_windowing_app_handle_education"
+ namespace: "lse_desktop_experience"
+ description: "Enables desktop windowing app handle education"
+ bug: "348208342"
+}
+
+flag {
+ name: "enable_compat_ui_visibility_status"
+ namespace: "lse_desktop_experience"
+ description: "Enables the tracking of the status for compat ui elements."
+ bug: "350953004"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 6b24545..5397e91 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -141,6 +141,13 @@
}
flag {
+ name: "respect_non_top_visible_fixed_orientation"
+ namespace: "windowing_frontend"
+ description: "If top activity is not opaque, respect the fixed orientation of activity behind it"
+ bug: "283514860"
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
@@ -199,3 +206,22 @@
}
}
+flag {
+ name: "enforce_shell_thread_model"
+ namespace: "windowing_frontend"
+ description: "Crash the shell process if someone calls in from the wrong thread"
+ bug: "351189446"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+flag {
+ name: "custom_animations_behind_translucent"
+ namespace: "windowing_frontend"
+ description: "A change can use its own layer parameters to animate behind a translucent activity"
+ bug: "327332488"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 7a8a47e..9d57041 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -342,8 +342,8 @@
.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
.toBundle();
try {
- mOnUnsuspend.sendIntent(this, 0, null, null, null, null,
- activityOptions);
+ mOnUnsuspend.sendIntent(this, 0, null, null, activityOptions,
+ null, null);
} catch (IntentSender.SendIntentException e) {
Slog.e(TAG, "Error while starting intent " + mOnUnsuspend, e);
}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 63623c7..82ae76a 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -43,6 +43,7 @@
void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
+ void onImeSwitchButtonClickFromClient(int displayId, int userId);
void notifyUserActionAsync();
void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
in ImeTracker.Token statsToken);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 72c41be..f50f02e 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -378,6 +378,22 @@
}
/**
+ * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int, int)}
+ */
+ @AnyThread
+ public void onImeSwitchButtonClickFromClient(int displayId, int userId) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.onImeSwitchButtonClickFromClient(displayId, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Calls {@link IInputMethodPrivilegedOperations#notifyUserActionAsync()}
*/
@AnyThread
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1638699..3e6f18e 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -160,8 +160,13 @@
*/
public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
- /** Track entering desktop mode interaction. */
- public static final int CUJ_DESKTOP_MODE_ENTER_MODE = 107;
+ /**
+ * Track entering desktop mode interaction via app handle drag.
+ *
+ * <p>Tracking starts when the app handle is dragged and
+ * finishes when the window animation to desktop ends after app handle release.
+ */
+ public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG = 107;
/** Track exiting desktop mode interaction. */
public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
@@ -175,9 +180,25 @@
/** Track launching a dialog from a status bar chip. */
public static final int CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP = 111;
+ /**
+ * Track entering desktop mode interaction via app handle menu.
+ *
+ * <p>Tracking starts after windowing mode option in the app handle menu is clicked and
+ * finishes when the window animation to desktop ends.
+ */
+ public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU = 112;
+
+ /** Track Launcher Keyboard Quick Switch View opening animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN = 113;
+
+ /** Track Launcher Keyboard Quick Switch View closing animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE = 114;
+
+ /** Track launching an app through the Launcher Keyboard Quick Switch View */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
/** @hide */
@IntDef({
@@ -276,11 +297,15 @@
CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
CUJ_FOLD_ANIM,
CUJ_DESKTOP_MODE_RESIZE_WINDOW,
- CUJ_DESKTOP_MODE_ENTER_MODE,
+ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG,
+ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU,
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
CUJ_DESKTOP_MODE_DRAG_WINDOW,
- CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP
+ CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -389,11 +414,15 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
}
private Cuj() {
@@ -602,8 +631,10 @@
return "FOLD_ANIM";
case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
return "DESKTOP_MODE_RESIZE_WINDOW";
- case CUJ_DESKTOP_MODE_ENTER_MODE:
- return "DESKTOP_MODE_ENTER_MODE";
+ case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG:
+ return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG";
+ case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU:
+ return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU";
case CUJ_DESKTOP_MODE_EXIT_MODE:
return "DESKTOP_MODE_EXIT_MODE";
case CUJ_DESKTOP_MODE_MINIMIZE_WINDOW:
@@ -612,6 +643,12 @@
return "DESKTOP_MODE_DRAG_WINDOW";
case CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP:
return "STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 30fa4f1..2ad6651 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -12,7 +12,7 @@
flag {
name: "use_transaction_codes_for_unknown_methods"
- namespace: "dropbox"
+ namespace: "stability"
description: "Use transaction codes when the method names is unknown"
bug: "350041302"
is_fixed_read_only: true
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 f306b0b..b873175 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -60,7 +60,13 @@
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
+ public enum Permission {
+ READ_WRITE,
+ READ_ONLY
+ }
+
private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+ private final ArrayMap<String, Permission> mFlagPermissions = new ArrayMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
@@ -184,6 +190,12 @@
Slog.v(LOG_TAG, "Read Aconfig default flag value "
+ flagPackageAndName + " = " + flagValue);
mFlagValues.put(flagPackageAndName, flagValue);
+
+ Permission permission = flag.permission == Aconfig.READ_ONLY
+ ? Permission.READ_ONLY
+ : Permission.READ_WRITE;
+
+ mFlagPermissions.put(flagPackageAndName, permission);
}
}
@@ -200,6 +212,17 @@
}
/**
+ * Get the flag permission, or null if the flag doesn't exist.
+ * @param flagPackageAndName Full flag name formatted as 'package.flag'
+ * @return the current permission of the given Aconfig flag, or null if there is no such flag
+ */
+ @Nullable
+ public Permission getFlagPermission(@NonNull String flagPackageAndName) {
+ Permission permission = mFlagPermissions.get(flagPackageAndName);
+ return permission;
+ }
+
+ /**
* Check if the element in {@code parser} should be skipped because of the feature flag.
* @param parser XML parser object currently parsing an element
* @return true if the element is disabled because of its feature flag
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 63ff598..87e22ed 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -109,6 +109,7 @@
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
+import com.android.window.flags.Flags;
import java.util.List;
import java.util.function.Consumer;
@@ -1193,7 +1194,8 @@
// If we should always consume system bars, only consume that if the app wanted to go to
// fullscreen, as otherwise we can expect the app to handle it.
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
- || (attrs.flags & FLAG_FULLSCREEN) != 0
+ || (attrs.flags & FLAG_FULLSCREEN) != 0;
+ final boolean hideStatusBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
boolean consumingStatusBar =
((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
@@ -1203,9 +1205,20 @@
&& mForceWindowDrawsBarBackgrounds
&& mLastTopInset != 0)
|| ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
- && fullscreen);
+ && hideStatusBar);
- int consumedTop = consumingStatusBar ? mLastTopInset : 0;
+ final boolean hideCaptionBar = fullscreen
+ || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
+ final boolean consumingCaptionBar =
+ ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+ && hideCaptionBar);
+
+ final int consumedTop;
+ if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
+ } else {
+ consumedTop = consumingStatusBar ? mLastTopInset : 0;
+ }
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 07be700..652cba7 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -98,14 +98,15 @@
this::onTracingFlush,
this::onTracingInstanceStop
);
+ @Nullable
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private final Runnable mCacheUpdater;
private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
- private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
- private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
+ private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+ private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
@@ -126,7 +127,7 @@
}
public PerfettoProtoLogImpl(
- ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
@@ -136,8 +137,8 @@
@VisibleForTesting
public PerfettoProtoLogImpl(
- ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- ProtoLogViewerConfigReader viewerConfigReader,
+ @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @Nullable ProtoLogViewerConfigReader viewerConfigReader,
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
@@ -168,12 +169,124 @@
log(logLevel, group, new Message(messageString), args);
}
+ /**
+ * SLog wrapper.
+ */
+ @VisibleForTesting
+ public void passToLogcat(String tag, LogLevel level, String message) {
+ switch (level) {
+ case DEBUG:
+ Slog.d(tag, message);
+ break;
+ case VERBOSE:
+ Slog.v(tag, message);
+ break;
+ case INFO:
+ Slog.i(tag, message);
+ break;
+ case WARN:
+ Slog.w(tag, message);
+ break;
+ case ERROR:
+ Slog.e(tag, message);
+ break;
+ case WTF:
+ Slog.wtf(tag, message);
+ break;
+ }
+ }
+
+ /**
+ * Returns {@code true} iff logging to proto is enabled.
+ */
+ public boolean isProtoEnabled() {
+ return mTracingInstances.get() > 0;
+ }
+
+ /**
+ * Start text logging
+ * @param groups Groups to start text logging for
+ * @param logger A logger to write status updates to
+ * @return status code
+ */
+ public int startLoggingToLogcat(String[] groups, ILogger logger) {
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.loadViewerConfig(groups, logger);
+ }
+ return setTextLogging(true, logger, groups);
+ }
+
+ /**
+ * Stop text logging
+ * @param groups Groups to start text logging for
+ * @param logger A logger to write status updates to
+ * @return status code
+ */
+ public int stopLoggingToLogcat(String[] groups, ILogger logger) {
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.unloadViewerConfig(groups, logger);
+ }
+ return setTextLogging(false, logger, groups);
+ }
+
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ final int[] groupLevelCount = mLogLevelCounts.get(group.name());
+ return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
+ || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
+ || group.isLogToLogcat();
+ }
+
+ @Override
+ public void registerGroups(IProtoLogGroup... protoLogGroups) {
+ for (IProtoLogGroup protoLogGroup : protoLogGroups) {
+ mLogGroups.put(protoLogGroup.name(), protoLogGroup);
+ }
+ }
+
+ /**
+ * Responds to a shell command.
+ */
+ public int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArg();
+ if (cmd == null) {
+ return unknownCommand(pw);
+ }
+ ArrayList<String> args = new ArrayList<>();
+ String arg;
+ while ((arg = shell.getNextArg()) != null) {
+ args.add(arg);
+ }
+ final ILogger logger = (msg) -> logAndPrintln(pw, msg);
+ String[] groups = args.toArray(new String[0]);
+ switch (cmd) {
+ case "start", "stop" -> {
+ pw.println("Command not supported. "
+ + "Please start and stop ProtoLog tracing with Perfetto.");
+ return -1;
+ }
+ case "enable-text" -> {
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.loadViewerConfig(groups, logger);
+ }
+ return setTextLogging(true, logger, groups);
+ }
+ case "disable-text" -> {
+ return setTextLogging(false, logger, groups);
+ }
+ default -> {
+ return unknownCommand(pw);
+ }
+ }
+ }
+
private void log(LogLevel logLevel, IProtoLogGroup group, Message message,
@Nullable Object[] args) {
if (isProtoEnabled()) {
long tsNanos = SystemClock.elapsedRealtimeNanos();
final String stacktrace;
- if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
+ if (mCollectStackTraceGroupCounts.getOrDefault(group.name(), 0) > 0) {
stacktrace = collectStackTrace();
} else {
stacktrace = null;
@@ -314,7 +427,12 @@
private void logToLogcat(String tag, LogLevel level, Message message,
@Nullable Object[] args) {
- String messageString = message.getMessage(mViewerConfigReader);
+ String messageString;
+ if (mViewerConfigReader == null) {
+ messageString = message.getMessage();
+ } else {
+ messageString = message.getMessage(mViewerConfigReader);
+ }
if (messageString == null) {
StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
@@ -346,7 +464,7 @@
} catch (IllegalArgumentException e) {
message = "FORMAT_ERROR \"" + messageString + "\", args=("
+ String.join(
- ", ", Arrays.stream(args).map(Object::toString).toList()) + ")";
+ ", ", Arrays.stream(args).map(Object::toString).toList()) + ")";
}
} else {
message = messageString;
@@ -354,33 +472,6 @@
passToLogcat(tag, level, message);
}
- /**
- * SLog wrapper.
- */
- @VisibleForTesting
- public void passToLogcat(String tag, LogLevel level, String message) {
- switch (level) {
- case DEBUG:
- Slog.d(tag, message);
- break;
- case VERBOSE:
- Slog.v(tag, message);
- break;
- case INFO:
- Slog.i(tag, message);
- break;
- case WARN:
- Slog.w(tag, message);
- break;
- case ERROR:
- Slog.e(tag, message);
- break;
- case WTF:
- Slog.wtf(tag, message);
- break;
- }
- }
-
private void logToProto(LogLevel level, IProtoLogGroup logGroup, Message message, Object[] args,
long tsNanos, @Nullable String stacktrace) {
mDataSource.trace(ctx -> {
@@ -627,66 +718,6 @@
return internMap.get(string);
}
- /**
- * Returns {@code true} iff logging to proto is enabled.
- */
- public boolean isProtoEnabled() {
- return mTracingInstances.get() > 0;
- }
-
- /**
- * Start text logging
- * @param groups Groups to start text logging for
- * @param logger A logger to write status updates to
- * @return status code
- */
- public int startLoggingToLogcat(String[] groups, ILogger logger) {
- mViewerConfigReader.loadViewerConfig(logger);
- return setTextLogging(true, logger, groups);
- }
-
- /**
- * Stop text logging
- * @param groups Groups to start text logging for
- * @param logger A logger to write status updates to
- * @return status code
- */
- public int stopLoggingToLogcat(String[] groups, ILogger logger) {
- mViewerConfigReader.unloadViewerConfig();
- return setTextLogging(false, logger, groups);
- }
-
- @Override
- public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- final int[] groupLevelCount = mLogLevelCounts.get(group);
- return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
- || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
- || group.isLogToLogcat();
- }
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- for (IProtoLogGroup protoLogGroup : protoLogGroups) {
- mLogGroups.put(protoLogGroup.name(), protoLogGroup);
- }
- }
-
- /**
- * Start logging the stack trace of the when the log message happened for target groups
- * @return status code
- */
- public int startLoggingStackTrace(String[] groups, ILogger logger) {
- return -1;
- }
-
- /**
- * Stop logging the stack trace of the when the log message happened for target groups
- * @return status code
- */
- public int stopLoggingStackTrace() {
- return -1;
- }
-
private int setTextLogging(boolean value, ILogger logger, String... groups) {
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
@@ -703,41 +734,6 @@
return 0;
}
- /**
- * Responds to a shell command.
- */
- public int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArg();
- if (cmd == null) {
- return unknownCommand(pw);
- }
- ArrayList<String> args = new ArrayList<>();
- String arg;
- while ((arg = shell.getNextArg()) != null) {
- args.add(arg);
- }
- final ILogger logger = (msg) -> logAndPrintln(pw, msg);
- String[] groups = args.toArray(new String[0]);
- switch (cmd) {
- case "start", "stop" -> {
- pw.println("Command not supported. "
- + "Please start and stop ProtoLog tracing with Perfetto.");
- return -1;
- }
- case "enable-text" -> {
- mViewerConfigReader.loadViewerConfig(logger);
- return setTextLogging(true, logger, groups);
- }
- case "disable-text" -> {
- return setTextLogging(false, logger, groups);
- }
- default -> {
- return unknownCommand(pw);
- }
- }
- }
-
private int unknownCommand(PrintWriter pw) {
pw.println("Unknown command");
pw.println("Window manager logging options:");
@@ -755,15 +751,8 @@
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
for (String overriddenGroupTag : overriddenGroupTags) {
- IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
- if (group == null) {
- throw new IllegalArgumentException("Trying to set config for \""
- + overriddenGroupTag + "\" that isn't registered");
- }
-
- mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ mLogLevelCounts.putIfAbsent(overriddenGroupTag, new int[LogLevel.values().length]);
+ final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
@@ -771,13 +760,13 @@
}
if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+ mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+ mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
}
if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+ mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+ mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
}
}
@@ -797,24 +786,22 @@
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
for (String overriddenGroupTag : overriddenGroupTags) {
- IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+ for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
logLevelsCountsForGroup[i]--;
}
if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
- mLogLevelCounts.remove(group);
+ mLogLevelCounts.remove(overriddenGroupTag);
}
if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.get(group) - 1);
+ mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+ mCollectStackTraceGroupCounts.get(overriddenGroupTag) - 1);
- if (mCollectStackTraceGroupCounts.get(group) == 0) {
- mCollectStackTraceGroupCounts.remove(group);
+ if (mCollectStackTraceGroupCounts.get(overriddenGroupTag) == 0) {
+ mCollectStackTraceGroupCounts.remove(overriddenGroupTag);
}
}
}
@@ -822,7 +809,7 @@
mCacheUpdater.run();
}
- static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+ private static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Slog.i(LOG_TAG, msg);
if (pw != null) {
pw.println(msg);
@@ -852,7 +839,11 @@
return mMessageMask;
}
- private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) {
+ private String getMessage() {
+ return mMessageString;
+ }
+
+ private String getMessage(@NonNull ProtoLogViewerConfigReader viewerConfigReader) {
if (mMessageString != null) {
return mMessageString;
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index b7b2424..bb6c8b7 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -1,20 +1,32 @@
package com.android.internal.protolog;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
-import android.util.ArrayMap;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.LongSparseArray;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
import java.io.IOException;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
public class ProtoLogViewerConfigReader {
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private Map<Long, String> mLogMessageMap = null;
+ private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
+ private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
public ProtoLogViewerConfigReader(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
@@ -26,39 +38,62 @@
* or the viewer config is not loaded into memory.
*/
public synchronized String getViewerString(long messageHash) {
- if (mLogMessageMap != null) {
- return mLogMessageMap.get(messageHash);
- } else {
- return null;
- }
+ return mLogMessageMap.get(messageHash);
+ }
+
+ public synchronized void loadViewerConfig(String[] groups) {
+ loadViewerConfig(groups, (message) -> {});
}
/**
* Loads the viewer config into memory. No-op if already loaded in memory.
*/
- public synchronized void loadViewerConfig(ILogger logger) {
- if (mLogMessageMap != null) {
- return;
- }
+ public synchronized void loadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (mGroupHashes.containsKey(group)) {
+ continue;
+ }
- try {
- doLoadViewerConfig();
- logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
- } catch (IOException e) {
- logger.log("Unable to load log definitions: "
- + "IOException while processing viewer config" + e);
+ try {
+ Map<Long, String> mappings = loadViewerConfigMappingForGroup(group);
+ mGroupHashes.put(group, mappings.keySet());
+ for (Long key : mappings.keySet()) {
+ mLogMessageMap.put(key, mappings.get(key));
+ }
+
+ logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
+ } catch (IOException e) {
+ logger.log("Unable to load log definitions: "
+ + "IOException while processing viewer config" + e);
+ }
}
}
+ public synchronized void unloadViewerConfig(String[] groups) {
+ unloadViewerConfig(groups, (message) -> {});
+ }
+
/**
* Unload the viewer config from memory.
*/
- public synchronized void unloadViewerConfig() {
- mLogMessageMap = null;
+ public synchronized void unloadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (!mGroupHashes.containsKey(group)) {
+ continue;
+ }
+
+ final Set<Long> hashes = mGroupHashes.get(group);
+ for (Long hash : hashes) {
+ logger.log("Unloading viewer config hash " + hash);
+ mLogMessageMap.remove(hash);
+ }
+ }
}
- private void doLoadViewerConfig() throws IOException {
- mLogMessageMap = new ArrayMap<>();
+ private Map<Long, String> loadViewerConfigMappingForGroup(String group) throws IOException {
+ Long targetGroupId = loadGroupId(group);
+
+ final Map<Long, String> hashesForGroup = new TreeMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -67,6 +102,7 @@
long messageId = 0;
String message = null;
+ int groupId = 0;
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (pis.getFieldNumber()) {
case (int) MESSAGE_ID:
@@ -75,9 +111,16 @@
case (int) MESSAGE:
message = pis.readString(MESSAGE);
break;
+ case (int) GROUP_ID:
+ groupId = pis.readInt(GROUP_ID);
+ break;
}
}
+ if (groupId == 0) {
+ throw new IOException("Failed to get group id");
+ }
+
if (messageId == 0) {
throw new IOException("Failed to get message id");
}
@@ -86,10 +129,45 @@
throw new IOException("Failed to get message string");
}
- mLogMessageMap.put(messageId, message);
+ if (groupId == targetGroupId) {
+ hashesForGroup.put(messageId, message);
+ }
pis.end(inMessageToken);
}
}
+
+ return hashesForGroup;
+ }
+
+ private Long loadGroupId(String group) throws IOException {
+ final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) GROUPS) {
+ final long inMessageToken = pis.start(GROUPS);
+
+ long groupId = 0;
+ String groupName = null;
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID:
+ groupId = pis.readInt(ID);
+ break;
+ case (int) NAME:
+ groupName = pis.readString(NAME);
+ break;
+ }
+ }
+
+ if (Objects.equals(groupName, group)) {
+ return groupId;
+ }
+
+ pis.end(inMessageToken);
+ }
+ }
+
+ throw new RuntimeException("Group " + group + "not found in viewer config");
}
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2b3ffeb2..cba27ce 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -135,6 +135,19 @@
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
boolean isInputMethodPickerShownForTest();
+ /**
+ * Called when the IME switch button was clicked from the system. Depending on the number of
+ * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
+ * method picker dialog.
+ *
+ * @param displayId The ID of the display where the input method picker dialog should be shown.
+ * @param userId The ID of the user that triggered the click.
+ */
+ @EnforcePermission("WRITE_SECURE_SETTINGS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ oneway void onImeSwitchButtonClickFromSystem(int displayId);
+
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
@nullable InputMethodSubtype getCurrentInputMethodSubtype(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f4ad487..19c6f51 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -22,6 +22,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.security.Flags.reportPrimaryAuthAttempts;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -414,7 +416,9 @@
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ false, userId);
+ }
}
@UnsupportedAppUsage
@@ -423,7 +427,9 @@
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ true, userId);
+ }
}
public void reportPasswordLockout(int timeoutMs, int userId) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index eaff760..7c62615 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,7 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,8 +42,10 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
+#include <thread>
#include <optional>
#include <sstream>
+#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -261,6 +264,13 @@
jfieldID mMixerBehavior;
} gAudioMixerAttributesField;
+static struct {
+ jclass clazz;
+ jmethodID run;
+} gRunnableClassInfo;
+
+static JavaVM* gVm;
+
static Mutex gLock;
enum AudioError {
@@ -2718,12 +2728,11 @@
}
static jint android_media_AudioSystem_getMaxSampleRate(JNIEnv *env, jobject thiz) {
- // see frameworks/av/services/audiopolicy/common/include/policy.h
- return 192000; // SAMPLE_RATE_HZ_MAX (for API)
+ return SAMPLE_RATE_HZ_MAX;
}
static jint android_media_AudioSystem_getMinSampleRate(JNIEnv *env, jobject thiz) {
- return 4000; // SAMPLE_RATE_HZ_MIN (for API)
+ return SAMPLE_RATE_HZ_MIN;
}
static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jArray) {
@@ -3363,6 +3372,55 @@
return enabled;
}
+class JavaSystemPropertyListener {
+ public:
+ JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
+ mCallback(env->NewGlobalRef(javaCallback)),
+ mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
+ mListenerThread = std::thread([this]() mutable {
+ JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
+ while (!mCleanupSignal.load()) {
+ using namespace std::chrono_literals;
+ // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
+ // be destroyed.
+ std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+ if (newVal != "" && mLastVal != newVal) {
+ threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
+ mLastVal = std::move(newVal);
+ }
+ }
+ });
+ }
+
+ ~JavaSystemPropertyListener() {
+ mCleanupSignal.store(true);
+ mListenerThread.join();
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
+ env->DeleteGlobalRef(mCallback);
+ }
+
+ private:
+ jobject mCallback;
+ android::base::CachedProperty mCachedProperty;
+ std::thread mListenerThread;
+ std::atomic<bool> mCleanupSignal{false};
+ std::string mLastVal = "";
+};
+
+std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
+std::mutex gSysPropLock{};
+
+static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
+ jstring sysProp,
+ jobject javaCallback) {
+ ScopedUtfChars sysPropChars{env, sysProp};
+ auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
+ std::string{sysPropChars.c_str()});
+ std::unique_lock _l{gSysPropLock};
+ gSystemPropertyListeners.push_back(std::move(listener));
+}
+
+
// ----------------------------------------------------------------------------
#define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3535,7 +3593,12 @@
android_media_AudioSystem_clearPreferredMixerAttributes),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
- MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+ MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+ "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+ android_media_AudioSystem_listenForSystemPropertyChange),
+
+ };
static const JNINativeMethod gEventHandlerMethods[] =
{MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3817,6 +3880,12 @@
gAudioMixerAttributesField.mMixerBehavior =
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
+ jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
+ gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
+ gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
+
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
+
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
}
env->ReleaseStringUTFChars(file, file8);
- // Most proc files we read are small, so we only go through the
- // loop once and use the stack buffer. We allocate a buffer big
+ // Most proc files we read are small, so we go through the loop
+ // with the stack buffer firstly. We allocate a buffer big
// enough for the whole file.
char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
char* readBuffer = &readBufferStack[0];
ssize_t readBufferSize = kProcReadStackBufferSize;
ssize_t numberBytesRead;
+ off_t offset = 0;
for (;;) {
+ ssize_t requestedBufferSize = readBufferSize - offset;
// By using pread, we can avoid an lseek to rewind the FD
// before retry, saving a system call.
- numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
- if (numberBytesRead < 0 && errno == EINTR) {
- continue;
- }
+ numberBytesRead =
+ TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
if (numberBytesRead < 0) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+ ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+ strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+ fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead < readBufferSize) {
+ if (numberBytesRead == 0) {
+ // End of file.
+ numberBytesRead = offset;
break;
}
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ if (numberBytesRead < requestedBufferSize) {
+ // Read less bytes than requested, it's not an error per pread(2).
+ offset += numberBytesRead;
+ } else {
+ // Buffer is fully used, try to grow it.
+ if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+ readBufferHeap.reset(); // Free address space before getting more.
+ readBufferHeap = std::make_unique<char[]>(readBufferSize);
+ if (!readBufferHeap) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return JNI_FALSE;
+ }
+ readBuffer = readBufferHeap.get();
+ offset = 0;
}
- readBufferSize = std::max(readBufferSize * 2,
- kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
}
// parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 59d18b8..30c926c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -104,6 +104,7 @@
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
@@ -151,6 +152,7 @@
{"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)},
{"android.view.InputDevice", REG_JNI(register_android_view_InputDevice)},
{"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)},
+ {"android.view.Surface", REG_JNI(register_android_view_Surface)},
{"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index b75d545..eddf1d2 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -47,4 +47,5 @@
optional int32 ime_window_visibility = 22;
optional bool show_ime_with_hard_keyboard = 23;
optional bool accessibility_requesting_no_soft_keyboard = 24;
-}
\ No newline at end of file
+ optional bool concurrent_multi_user_mode_enabled = 25;
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4892f59..0975eda 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4304,7 +4304,7 @@
<attr name="settingsActivity" format="string" />
<!-- Component name of an activity that allows the user to modify
on-screen keyboards variants (e.g. different language or layout) for this service. -->
- <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+ <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
<attr name="languageSettingsActivity" format="string"/>
<!-- Set to true in all of the configurations for which this input
method should be considered an option as the default. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f94c8ab..2e3dbda 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1052,6 +1052,19 @@
<!-- The font weight adjustment value has changed. Used to reflect the user increasing font
weight. -->
<flag name="fontWeightAdjustment" value="0x10000000" />
+ <!-- The assets paths have changed. For example a runtime overlay is installed and enabled.
+ Corresponds to {@link android.content.pm.ActivityInfo#CONFIG_ASSETS_PATHS}. -->
+ <flag name="assetsPaths" value="0x80000000" />
+ <!-- This is probably not the flag you want, the resources compiler supports a less
+ dangerous version of it, 'allKnown', that only suppresses all currently existing
+ configuration change restarts depending on your target SDK rather than whatever the
+ latest SDK supports, allowing the application to work with resources on future Platform
+ versions.
+ Activity doesn't use Android Resources at all and doesn't need to be restarted on any
+ configuration changes. This overrides all other flags, and this is recommended to be
+ used individually. Corresponds to
+ {@link android.content.pm.ActivityInfo#CONFIG_RESOURCES_UNUSED}. -->
+ <flag name="resourcesUnused" value="0x8000000" />
</attr>
<!-- Indicate that the activity can be launched as the embedded child of another
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index cdd8557..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,12 +393,6 @@
<bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
<java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
- <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
- existing cellular networks already supports IWLAN.
- -->
- <bool name="force_iwlan_mms_feature_enabled">false</bool>
- <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
-
<!-- The time duration in millis after which Telephony will abort the last message datagram
sending requests. Telephony starts a timer when receiving a last message datagram sending
request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b64334f..b74b41c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -114,7 +114,7 @@
<public name="optional"/>
<!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
<public name="adServiceTypes" />
- <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+ <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
<public name="languageSettingsActivity"/>
<!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
<public name="dreamCategory"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6b71f97..46b15416 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6534,4 +6534,23 @@
<string name="bg_user_sound_notification_button_mute">Mute</string>
<!-- Notification text to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
<string name="bg_user_sound_notification_message">Tap to mute sound</string>
+
+ <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_browser">Browser</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_email">Email</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_sms">SMS</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_music">Music</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calculator app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calculator">Calculator</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_maps">Maps</string>
+ <!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications">Applications</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d25f59d..c50b961 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5561,4 +5561,15 @@
<java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
<java-symbol type="string" name="bg_user_sound_notification_button_mute" />
<java-symbol type="string" name="bg_user_sound_notification_message" />
+
+ <!-- Keyboard Shortcut default category names. -->
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_browser" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calculator" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calendar" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_contacts" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_email" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_maps" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_music" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_sms" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications" />
</resources>
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 36ab0d4..ce85a76 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -70,7 +70,7 @@
assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
assertThat(imi.supportsStylusHandwriting(), is(false));
assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
- if (Flags.imeSwitcherRevamp()) {
+ if (Flags.imeSwitcherRevampApi()) {
assertThat(imi.createImeLanguageSettingsActivityIntent(), equalTo(null));
}
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index f87a9e2..e8a0762 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,8 +25,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -810,7 +808,6 @@
@Test
public void testActivityWindowInfoChanged_activityLaunch() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
@@ -825,7 +822,6 @@
@Test
public void testActivityWindowInfoChanged_activityRelaunch() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
@@ -866,7 +862,6 @@
@Test
public void testActivityWindowInfoChanged_activityConfigurationChanged() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 0b270d4..d2a444f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -53,8 +53,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.window.flags.Flags;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -167,8 +165,6 @@
@Test
public void testActivityWindowInfoChangedListener() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
diff --git a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
index 064439e..6998c32 100644
--- a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
+++ b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -74,4 +75,33 @@
assertFalse("IME shouldn't restart since it handles configChanges",
didReset.get());
}
+
+ @Test
+ @RequiresFlagsEnabled(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+ public void testShouldImeRestart_handleResourceUnused() throws Exception {
+ Configuration config = mContext.getResources().getConfiguration();
+ mImsConfigTracker.onInitialize(0 /* handledConfigChanges */);
+ mImsConfigTracker.onBindInput(mContext.getResources());
+ Configuration newConfig = new Configuration(config);
+
+ final AtomicBoolean didReset = new AtomicBoolean();
+ Runnable resetStateRunner = () -> didReset.set(true);
+
+ mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+ assertFalse("IME shouldn't restart if config hasn't changed",
+ didReset.get());
+
+ // Screen density changed but IME doesn't handle configChanges
+ newConfig.densityDpi = 99;
+ mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+ assertTrue("IME should restart for unhandled configChanges",
+ didReset.get());
+
+ didReset.set(false);
+ // opt-in IME to handle all configuration changes.
+ mImsConfigTracker.setHandledConfigChanges(ActivityInfo.CONFIG_RESOURCES_UNUSED);
+ mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+ assertFalse("IME shouldn't restart since it handles configChanges",
+ didReset.get());
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index c01b51d..b68ff78 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -29,11 +29,9 @@
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
@@ -100,9 +98,8 @@
}
@Test
- @RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
- FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
- public void frameRateChangesWhenContentMoves() throws Throwable {
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
+ public void highHintWhenContentMoves() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
}
@@ -110,21 +107,15 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.offsetLeftAndRight(100);
runAfterDraw(() -> {
- if (toolkitFrameRateVelocityMappingReadOnly()) {
- float frameRate = mViewRoot.getLastPreferredFrameRate();
- assertTrue(frameRate > 0);
- } else {
- assertEquals(FRAME_RATE_CATEGORY_HIGH,
- mViewRoot.getLastPreferredFrameRateCategory());
- }
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+ mViewRoot.getLastPreferredFrameRateCategory());
});
});
waitForAfterDraw();
}
@Test
- @RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
- FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
public void inputMethodWithContentMoves() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
@@ -136,6 +127,8 @@
final WindowManager.LayoutParams attrs = mViewRoot.mWindowAttributes;
attrs.type = TYPE_INPUT_METHOD;
instrumentation.runOnMainSync(() -> {
+ attrs.width = 1;
+ attrs.height = 1;
mViewRoot.setLayoutParams(attrs, false);
});
instrumentation.waitForIdleSync();
@@ -147,14 +140,13 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.offsetLeftAndRight(100);
runAfterDraw(() -> {
- if (toolkitFrameRateVelocityMappingReadOnly()) {
- float frameRate = mViewRoot.getLastPreferredFrameRate();
- // frame rate shouldn't be boost with TYPE_INPUT_METHOD window type
- assertTrue(frameRate == 0);
- } else {
- assertEquals(FRAME_RATE_CATEGORY_HIGH,
- mViewRoot.getLastPreferredFrameRateCategory());
- }
+ int expected = toolkitFrameRateBySizeReadOnly()
+ ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
+ float frameRate = mViewRoot.getLastPreferredFrameRate();
+ // frame rate shouldn't be boost with TYPE_INPUT_METHOD window type
+ assertTrue(frameRate == 0);
+ assertEquals(expected,
+ mViewRoot.getLastPreferredFrameRateCategory());
});
});
waitForAfterDraw();
@@ -177,7 +169,7 @@
@Test
- @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
public void highHintWhenActionMove() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
@@ -227,62 +219,59 @@
move.setSource(InputDevice.SOURCE_TOUCHSCREEN);
instrumentation.sendPointerSync(move);
- // We should continue to enable touch boost even when GTE compatibility is present.
+ // Should continue to enable touch boost.
mActivityRule.runOnUiThread(() -> {
mMovingView.offsetLeftAndRight(10);
assertTrue(mViewRoot.getIsTouchBoosting());
});
- now = SystemClock.uptimeMillis();
- MotionEvent up = MotionEvent.obtain(
- now, // downTime
- now, // eventTime
- MotionEvent.ACTION_UP, // action
- position[0], // x
- position[1], // y
- 0 // metaState
- );
- up.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- instrumentation.sendPointerSync(up);
-
- // No touch boost when there is no ongoing pressed gesture.
- mActivityRule.runOnUiThread(() -> {
- mMovingView.offsetLeftAndRight(10);
- assertFalse(mViewRoot.getIsTouchBoosting());
- });
-
down.recycle();
move.recycle();
- up.recycle();
}
+ // When the size of a View is changed, we should boost the frame rate.
@Test
- @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
- public void frameBoostDisable() throws Throwable {
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
+ public void highHintWhenSizeChanged() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
}
- mActivityRule.runOnUiThread(() -> {
- long now = SystemClock.uptimeMillis();
- MotionEvent down = MotionEvent.obtain(
- /* downTime */ now,
- /* eventTime */ now,
- /* action */ MotionEvent.ACTION_DOWN,
- /* x */ 0f,
- /* y */ 0f,
- /* metaState */ 0
- );
- mActivity.dispatchTouchEvent(down);
- mMovingView.offsetLeftAndRight(10);
- });
- mActivityRule.runOnUiThread(() -> {
- mMovingView.invalidate();
- });
+ waitForFrameRateCategoryToSettle();
+ assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ int width = mMovingView.getWidth();
+ int height = mMovingView.getHeight();
+
+ ViewGroup.LayoutParams params = mMovingView.getLayoutParams();
+ params.width = width * 2;
+ params.height = height * 2;
+
+ // frame rate category should be HIGH_HINT when the size is changed
mActivityRule.runOnUiThread(() -> {
- assertFalse(mViewRoot.getIsTouchBoosting());
- assertFalse(mViewRoot.getIsFrameRateBoosting());
+ mMovingView.setLayoutParams(params);
+ runAfterDraw(() -> {
+ assertTrue(mMovingView.getWidth() > width);
+ assertTrue(mMovingView.getHeight() > height);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ });
});
+ waitForAfterDraw();
+
+ // set it back to the original size
+ params.width = width;
+ params.height = height;
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.setLayoutParams(params);
+ runAfterDraw(() -> {
+ assertEquals(width, mMovingView.getWidth());
+ assertEquals(height, mMovingView.getHeight());
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ });
+ });
+ waitForAfterDraw();
}
@Test
@@ -804,6 +793,12 @@
down.setSource(InputDevice.SOURCE_TOUCHSCREEN);
instrumentation.sendPointerSync(down);
assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
+
+ // Should still be boost with position changed
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.offsetLeftAndRight(10);
+ assertTrue(mViewRoot.getIsTouchBoosting());
+ });
}
@LargeTest
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index d4482f2..9ae96a0 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -348,12 +348,16 @@
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
+ assertTrue(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
waitForIdle();
verify(mCallback1).onBackCancelled();
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
+ // Verify that ProgressAnimator is reset (and thus does not cause further callback event
+ // dispatching)
+ assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
}
@Test
diff --git a/core/tests/overlaytests/handle_config_change/Android.bp b/core/tests/overlaytests/handle_config_change/Android.bp
new file mode 100644
index 0000000..2b31d0a
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_resources",
+}
+
+java_test_host {
+ name: "HandleConfigChangeHostTests",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ static_libs: [
+ "compatibility-host-util-axt",
+ "flag-junit-host",
+ "android.content.res.flags-aconfig-java-host",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ // All APKs required by the tests
+ data: [
+ ":OverlayResApp",
+ ],
+ per_testcase_directory: true,
+}
diff --git a/core/tests/overlaytests/handle_config_change/AndroidTest.xml b/core/tests/overlaytests/handle_config_change/AndroidTest.xml
new file mode 100644
index 0000000..05ea036
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Config for the handle config change test cases">
+ <option name="test-tag" value="HandleConfigChangeHostTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="set-global-setting" key="verifier_engprod" value="1" />
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="OverlayResApp.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="class" value="com.android.overlaytest.HandleConfigChangeHostTests" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java b/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java
new file mode 100644
index 0000000..e91716a
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest;
+
+import android.content.res.Flags;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class HandleConfigChangeHostTests extends BaseHostJUnit4Test {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+ private static final String DEVICE_TEST_PKG1 = "com.android.overlaytest.overlayresapp";
+ private static final String DEVICE_TEST_CLASS = "OverlayResTest";
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+ public void testOverlayRes() throws Exception {
+ runDeviceTests(DEVICE_TEST_PKG1, DEVICE_TEST_PKG1 + "." + DEVICE_TEST_CLASS,
+ "overlayRes_onConfigurationChanged");
+ }
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
new file mode 100644
index 0000000..e0f1012
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "OverlayResApp",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ platform_apis: true,
+ certificate: "platform",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ "truth",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+
+ manifest: "AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml
new file mode 100644
index 0000000..617e879
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.overlaytest.overlayresapp"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name=".OverlayResActivity"
+ android:exported="false"
+ android:configChanges="assetsPaths">
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.overlaytest.overlayresapp" />
+
+</manifest>
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml
new file mode 100644
index 0000000..493333f
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <integer name="test_integer">0</integer>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml
new file mode 100644
index 0000000..72820d3
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_name">My Application</string>
+ <string name="test_string">Test String</string>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java
new file mode 100644
index 0000000..96143ae
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.overlaytest.overlayresapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * A test activity to verify that the assets paths configuration changes are received if the
+ * overlay targeting state is changed.
+ */
+public class OverlayResActivity extends Activity {
+ private Runnable mConfigurationChangedCallback;
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ final Runnable callback = mConfigurationChangedCallback;
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ /** Registers the callback of onConfigurationChanged. */
+ public void setConfigurationChangedCallback(Runnable callback) {
+ mConfigurationChangedCallback = callback;
+ }
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java
new file mode 100644
index 0000000..1c37719
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.overlaytest.overlayresapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.util.TypedValue;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class OverlayResTest {
+ // Default timeout value
+ private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+ private static final String TEST_OVERLAY_NAME = "Test";
+ private static final String TEST_RESOURCE_INTEGER = "integer/test_integer";
+ private static final String TEST_RESOURCE_STRING = "string/test_string";
+ private static final int TEST_INTEGER = 0;
+ private static final int TEST_FRRO_INTEGER = 1;
+ private static final String TEST_STRING = "Test String";
+ private static final String TEST_FRRO_STRING = "FRRO Test String";
+ private OverlayResActivity mActivity;
+ private Context mContext;
+ private OverlayManager mOverlayManager;
+ private int mUserId;
+ private UserHandle mUserHandle;
+
+ @Rule
+ public ActivityScenarioRule<OverlayResActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(OverlayResActivity.class);
+
+ @Before
+ public void setUp() {
+ mActivityScenarioRule.getScenario().onActivity(activity -> {
+ assertThat(activity).isNotNull();
+ mActivity = activity;
+ });
+ mContext = mActivity.getApplicationContext();
+ mOverlayManager = mContext.getSystemService(OverlayManager.class);
+ mUserId = UserHandle.myUserId();
+ mUserHandle = UserHandle.of(mUserId);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+ mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+ info -> {
+ if (info.isFabricated()) {
+ cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+ }
+ });
+ mOverlayManager.commit(cleanUp.build());
+
+ }
+
+ @Test
+ public void overlayRes_onConfigurationChanged() throws Exception {
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ mActivity.setConfigurationChangedCallback(() -> {
+ Resources r = mActivity.getApplicationContext().getResources();
+ assertThat(r.getInteger(R.integer.test_integer)).isEqualTo(TEST_FRRO_INTEGER);
+ assertThat(r.getString(R.string.test_string)).isEqualTo(TEST_FRRO_STRING);
+ latch1.countDown();
+ });
+
+ // Create and enable FRRO
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE_INTEGER, TypedValue.TYPE_INT_DEC, TEST_FRRO_INTEGER)
+ .setResourceValue(TEST_RESOURCE_STRING, TypedValue.TYPE_STRING, TEST_FRRO_STRING)
+ .build();
+
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build());
+
+ OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+ assertNotNull(info);
+ assertThat(info.isEnabled()).isFalse();
+
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build());
+
+ info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+ assertNotNull(info);
+ assertThat(info.isEnabled()).isTrue();
+
+ if (!latch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Fail to wait configuration changes for build and enabling frro, "
+ + "onConfigurationChanged() has not been invoked.");
+ }
+
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ mActivity.setConfigurationChangedCallback(() -> {
+ Resources r = mActivity.getApplicationContext().getResources();
+ assertThat(r.getInteger(R.integer.test_integer)).isEqualTo(TEST_INTEGER);
+ assertThat(r.getString(R.string.test_string)).isEqualTo(TEST_STRING);
+ latch2.countDown();
+ });
+
+ // unregister FRRO
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .unregisterFabricatedOverlay(overlay.getIdentifier())
+ .build());
+
+ if (!latch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Fail to wait configuration changes after unregister frro,"
+ + " onConfigurationChanged() has not been invoked.");
+ }
+ }
+}
diff --git a/core/tests/resourceflaggingtests/OWNERS b/core/tests/resourceflaggingtests/OWNERS
new file mode 100644
index 0000000..10950a1
--- /dev/null
+++ b/core/tests/resourceflaggingtests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/RESOURCES_OWNERS
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9ea2943..1eb95c1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -70,10 +70,6 @@
@NonNull
private final TaskFragmentCallback mCallback;
- @VisibleForTesting
- @Nullable
- TaskFragmentAnimationController mAnimationController;
-
/**
* Callback that notifies the controller about changes to task fragments.
*/
@@ -91,25 +87,6 @@
mCallback = callback;
}
- @Override
- public void unregisterOrganizer() {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations();
- mAnimationController = null;
- }
- super.unregisterOrganizer();
- }
-
- /**
- * Overrides the animation for transitions of embedded activities organized by this organizer.
- */
- void overrideSplitAnimation() {
- if (mAnimationController == null) {
- mAnimationController = new TaskFragmentAnimationController(this);
- }
- mAnimationController.registerRemoteAnimations();
- }
-
/**
* Starts a new Activity and puts it into split with an existing Activity side-by-side.
* @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 7ddda1f..8e1fde0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -80,6 +81,7 @@
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -114,7 +116,6 @@
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
static final String TAG = "SplitController";
- static final boolean ENABLE_SHELL_TRANSITIONS = true;
// TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
// association. It's not set in WM Extensions nor Wm Jetpack library currently.
@@ -205,11 +206,9 @@
/** Listener registered to {@link ClientTransactionListenerController}. */
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
- Flags.activityWindowInfoFlag()
- ? this::onActivityWindowInfoChanged
- : null;
+ this::onActivityWindowInfoChanged;
private final Handler mHandler;
private final MainThreadExecutor mExecutor;
@@ -2556,9 +2555,9 @@
return ActivityThread.currentActivityThread().getActivity(activityToken);
}
- @VisibleForTesting
@Nullable
- ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ private ActivityThread.ActivityClientRecord getActivityClientRecord(
+ @NonNull Activity activity) {
return ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken());
}
@@ -3095,22 +3094,14 @@
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
- Objects.requireNonNull(activity);
synchronized (mLock) {
- if (Flags.activityWindowInfoFlag()) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
- }
- return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ return TaskFragmentOrganizer.isActivityEmbedded(activity);
}
}
@Override
public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
@NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
synchronized (mLock) {
@@ -3124,9 +3115,6 @@
@Override
public void clearEmbeddedActivityWindowInfoCallback() {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
if (mEmbeddedActivityWindowInfoCallback == null) {
return;
@@ -3147,9 +3135,6 @@
@Nullable
@Override
public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
- if (!Flags.activityWindowInfoFlag()) {
- return null;
- }
synchronized (mLock) {
final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
return activityWindowInfo != null
@@ -3180,15 +3165,6 @@
}
}
- @Nullable
- private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
- if (activity.isFinishing()) {
- return null;
- }
- final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
- return record != null ? record.getActivityWindowInfo() : null;
- }
-
@NonNull
private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
@NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index eb1fc23..ea60b15 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -166,11 +166,6 @@
mWindowLayoutComponent = windowLayoutComponent;
mController = controller;
registerOrganizer();
- if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell when Shell transition is enabled.
- overrideSplitAnimation();
- }
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
deleted file mode 100644
index 33220c4..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.graphics.Matrix.MTRANS_X;
-import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Choreographer;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import androidx.annotation.NonNull;
-
-/**
- * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
- *
- * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
- */
-class TaskFragmentAnimationAdapter {
-
- /**
- * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
- */
- private static final int LAYER_NO_OVERRIDE = -1;
-
- @NonNull
- final Animation mAnimation;
- @NonNull
- final RemoteAnimationTarget mTarget;
- @NonNull
- final SurfaceControl mLeash;
- /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
- @NonNull
- private final Rect mWholeAnimationBounds = new Rect();
- /**
- * Area in absolute coordinate that should represent all the content to show for this window.
- * This should be the end bounds for opening window, and start bounds for closing window in case
- * the window is resizing during the open/close transition.
- */
- @NonNull
- private final Rect mContentBounds = new Rect();
- /** Offset relative to the window parent surface for {@link #mContentBounds}. */
- @NonNull
- private final Point mContentRelOffset = new Point();
-
- @NonNull
- final Transformation mTransformation = new Transformation();
- @NonNull
- final float[] mMatrix = new float[9];
- @NonNull
- final float[] mVecs = new float[4];
- @NonNull
- final Rect mRect = new Rect();
- private boolean mIsFirstFrame = true;
- private int mOverrideLayer = LAYER_NO_OVERRIDE;
-
- TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target) {
- this(animation, target, target.leash, target.screenSpaceBounds);
- }
-
- /**
- * @param leash the surface to animate.
- * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't
- * go beyond.
- */
- TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
- @NonNull Rect wholeAnimationBounds) {
- mAnimation = animation;
- mTarget = target;
- mLeash = leash;
- mWholeAnimationBounds.set(wholeAnimationBounds);
- if (target.mode == MODE_CLOSING) {
- // When it is closing, we want to show the content at the start position in case the
- // window is resizing as well. For example, when the activities is changing from split
- // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
- final Rect startBounds = target.startBounds;
- final Rect endBounds = target.screenSpaceBounds;
- mContentBounds.set(startBounds);
- mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
- mContentRelOffset.offset(
- startBounds.left - endBounds.left,
- startBounds.top - endBounds.top);
- } else {
- mContentBounds.set(target.screenSpaceBounds);
- mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
- }
- }
-
- /**
- * Surface layer to be set at the first frame of the animation. We will not set the layer if it
- * is set to {@link #LAYER_NO_OVERRIDE}.
- */
- final void overrideLayer(int layer) {
- mOverrideLayer = layer;
- }
-
- /** Called on frame update. */
- final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
- if (mIsFirstFrame) {
- t.show(mLeash);
- if (mOverrideLayer != LAYER_NO_OVERRIDE) {
- t.setLayer(mLeash, mOverrideLayer);
- }
- mIsFirstFrame = false;
- }
-
- // Extract the transformation to the current time.
- mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
- mTransformation);
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- onAnimationUpdateInner(t);
- }
-
- /** To be overridden by subclasses to adjust the animation surface change. */
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- // Update the surface position and alpha.
- mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
-
- // Get current surface bounds in absolute coordinate.
- // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
- final int positionX = Math.round(mMatrix[MTRANS_X]);
- final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mContentBounds);
- cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
-
- // Store the current offset of the surface top left from (0,0) in absolute coordinate.
- final int offsetX = cropRect.left;
- final int offsetY = cropRect.top;
-
- // Intersect to make sure the animation happens within the whole animation bounds.
- if (!cropRect.intersect(mWholeAnimationBounds)) {
- // Hide the surface when it is outside of the animation area.
- t.setAlpha(mLeash, 0);
- }
-
- // cropRect is in absolute coordinate, so we need to translate it to surface top left.
- cropRect.offset(-offsetX, -offsetY);
- t.setCrop(mLeash, cropRect);
- }
-
- /** Called after animation finished. */
- final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
- onAnimationUpdate(t, mAnimation.getDuration());
- }
-
- final long getDurationHint() {
- return mAnimation.computeDurationHint();
- }
-
- /**
- * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
- * size change.
- */
- static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
-
- SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
- // Start leash is the snapshot of the starting surface.
- super(animation, target, target.startLeash, target.screenSpaceBounds);
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- // Snapshot should always be placed at the top left of the animation leash.
- mTransformation.getMatrix().postTranslate(0, 0);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
- }
- }
-
- /**
- * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
- */
- static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
-
- BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
- super(animation, target);
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- mTransformation.getMatrix().postTranslate(
- mTarget.localBounds.left, mTarget.localBounds.top);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
-
- // The following applies an inverse scale to the clip-rect so that it crops "after" the
- // scale instead of before.
- mVecs[1] = mVecs[2] = 0;
- mVecs[0] = mVecs[3] = 1;
- mTransformation.getMatrix().mapVectors(mVecs);
- mVecs[0] = 1.f / mVecs[0];
- mVecs[3] = 1.f / mVecs[3];
- final Rect clipRect = mTransformation.getClipRect();
- mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
- mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
- mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
- mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
- t.setWindowCrop(mLeash, mRect);
- }
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
deleted file mode 100644
index d7eb9a0..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-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_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-
-import android.util.Log;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/** Controls the TaskFragment remote animations. */
-class TaskFragmentAnimationController {
-
- private static final String TAG = "TaskFragAnimationCtrl";
- static final boolean DEBUG = false;
-
- private final TaskFragmentOrganizer mOrganizer;
- private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
- @VisibleForTesting
- final RemoteAnimationDefinition mDefinition;
- private boolean mIsRegistered;
-
- TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
- mOrganizer = organizer;
- mDefinition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter animationAdapter =
- new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
- }
-
- void registerRemoteAnimations() {
- if (DEBUG) {
- Log.v(TAG, "registerRemoteAnimations");
- }
- if (mIsRegistered) {
- return;
- }
- mOrganizer.registerRemoteAnimations(mDefinition);
- mIsRegistered = true;
- }
-
- void unregisterRemoteAnimations() {
- if (DEBUG) {
- Log.v(TAG, "unregisterRemoteAnimations");
- }
- if (!mIsRegistered) {
- return;
- }
- mOrganizer.unregisterRemoteAnimations();
- mIsRegistered = false;
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
deleted file mode 100644
index d9b73a8..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_CHANGING;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-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_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.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiFunction;
-
-/** To run the TaskFragment animations. */
-class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
-
- private static final String TAG = "TaskFragAnimationRunner";
- private final Handler mHandler;
- private final TaskFragmentAnimationSpec mAnimationSpec;
-
- TaskFragmentAnimationRunner() {
- HandlerThread animationThread = new HandlerThread(
- "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
- animationThread.start();
- mHandler = animationThread.getThreadHandler();
- mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
- }
-
- @Nullable
- private Animator mAnimator;
-
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] apps,
- @NonNull RemoteAnimationTarget[] wallpapers,
- @NonNull RemoteAnimationTarget[] nonApps,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- if (wallpapers.length != 0 || nonApps.length != 0) {
- throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
- + "wallpaper or non-app windows.");
- }
- if (TaskFragmentAnimationController.DEBUG) {
- Log.v(TAG, "onAnimationStart transit=" + transit);
- }
- mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
- }
-
- @Override
- public void onAnimationCancelled() {
- mHandler.post(this::cancelAnimation);
- }
-
- /** Creates and starts animation. */
- private void startAnimation(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- if (mAnimator != null) {
- Log.w(TAG, "start new animation when the previous one is not finished yet.");
- mAnimator.cancel();
- }
- mAnimator = createAnimator(transit, targets, finishedCallback);
- mAnimator.start();
- }
-
- /** Cancels animation. */
- private void cancelAnimation() {
- if (mAnimator == null) {
- return;
- }
- mAnimator.cancel();
- mAnimator = null;
- }
-
- /** Creates the animator given the transition type and windows. */
- @NonNull
- private Animator createAnimator(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- final List<TaskFragmentAnimationAdapter> adapters =
- createAnimationAdapters(transit, targets);
- long duration = 0;
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- duration = Math.max(duration, adapter.getDurationHint());
- }
- final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(duration);
- animator.addUpdateListener((anim) -> {
- // Update all adapters in the same transaction.
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
- }
- t.apply();
- });
- animator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {}
-
- @Override
- public void onAnimationEnd(Animator animation) {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- adapter.onAnimationEnd(t);
- }
- t.apply();
-
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- mAnimator = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {}
-
- @Override
- public void onAnimationRepeat(Animator animation) {}
- });
- return animator;
- }
-
- /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
- @NonNull
- private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
- @WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets) {
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- return createOpenAnimationAdapters(targets);
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- return createCloseAnimationAdapters(targets);
- case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
- return createChangeAnimationAdapters(targets);
- default:
- throw new IllegalArgumentException("Unhandled transit type=" + transit);
- }
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
- mAnimationSpec::loadOpenAnimation);
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
- mAnimationSpec::loadCloseAnimation);
- }
-
- /**
- * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
- * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
- */
- @NonNull
- private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
- @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
- // We need to know if the target window is only a partial of the whole animation screen.
- // If so, we will need to adjust it to make the whole animation screen looks like one.
- final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
- final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
- final Rect openingWholeScreenBounds = new Rect();
- final Rect closingWholeScreenBounds = new Rect();
- for (RemoteAnimationTarget target : targets) {
- if (target.mode != MODE_CLOSING) {
- openingTargets.add(target);
- openingWholeScreenBounds.union(target.screenSpaceBounds);
- } else {
- closingTargets.add(target);
- closingWholeScreenBounds.union(target.screenSpaceBounds);
- // Union the start bounds since this may be the ClosingChanging animation.
- closingWholeScreenBounds.union(target.startBounds);
- }
- }
-
- // For OPEN transition, open windows should be above close windows.
- // For CLOSE transition, open windows should be below close windows.
- int offsetLayer = TYPE_LAYER_OFFSET;
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
- for (RemoteAnimationTarget target : openingTargets) {
- final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
- animationProvider, openingWholeScreenBounds);
- if (isOpening) {
- adapter.overrideLayer(offsetLayer++);
- }
- adapters.add(adapter);
- }
- for (RemoteAnimationTarget target : closingTargets) {
- final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
- animationProvider, closingWholeScreenBounds);
- if (!isOpening) {
- adapter.overrideLayer(offsetLayer++);
- }
- adapters.add(adapter);
- }
- return adapters;
- }
-
- @NonNull
- private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
- @NonNull RemoteAnimationTarget target,
- @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
- @NonNull Rect wholeAnimationBounds) {
- final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
- return new TaskFragmentAnimationAdapter(animation, target, target.leash,
- wholeAnimationBounds);
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- if (shouldUseJumpCutForChangeAnimation(targets)) {
- return new ArrayList<>();
- }
-
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
- for (RemoteAnimationTarget target : targets) {
- if (target.mode == MODE_CHANGING) {
- // This is the target with bounds change.
- final Animation[] animations =
- mAnimationSpec.createChangeBoundsChangeAnimations(target);
- // Adapter for the starting snapshot leash.
- adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
- animations[0], target));
- // Adapter for the ending bounds changed leash.
- adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
- animations[1], target));
- continue;
- }
-
- // These are the other targets that don't have bounds change in the same transition.
- final Animation animation;
- if (target.hasAnimatingParent) {
- // No-op if it will be covered by the changing parent window.
- animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
- } else if (target.mode == MODE_CLOSING) {
- animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
- } else {
- animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
- }
- adapters.add(new TaskFragmentAnimationAdapter(animation, target));
- }
- return adapters;
- }
-
- /**
- * Whether we should use jump cut for the change transition.
- * This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
- * Uses a jump cut animation to simplify.
- */
- private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
- for (RemoteAnimationTarget target : targets) {
- if (target.hasAnimatingParent) {
- continue;
- }
- hasOpeningWindow |= target.mode == MODE_OPENING;
- hasClosingWindow |= target.mode == MODE_CLOSING;
- }
- return hasOpeningWindow && hasClosingWindow;
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
deleted file mode 100644
index 1f866c3..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-import com.android.internal.policy.AttributeCache;
-import com.android.internal.policy.TransitionAnimation;
-
-/** Animation spec for TaskFragment transition. */
-// TODO(b/206557124): provide an easier way to customize animation
-class TaskFragmentAnimationSpec {
-
- private static final String TAG = "TaskFragAnimationSpec";
- private static final int CHANGE_ANIMATION_DURATION = 517;
- private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
- private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
-
- private final Context mContext;
- private final TransitionAnimation mTransitionAnimation;
- private final Interpolator mFastOutExtraSlowInInterpolator;
- private final LinearInterpolator mLinearInterpolator;
- private float mTransitionAnimationScaleSetting;
-
- TaskFragmentAnimationSpec(@NonNull Handler handler) {
- mContext = ActivityThread.currentActivityThread().getApplication();
- mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
- // Initialize the AttributeCache for the TransitionAnimation.
- AttributeCache.init(mContext);
- mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.fast_out_extra_slow_in);
- mLinearInterpolator = new LinearInterpolator();
-
- // The transition animation should be adjusted based on the developer option.
- final ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
- new SettingsObserver(handler));
- }
-
- /** For target that doesn't need to be animated. */
- @NonNull
- static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
- // Noop but just keep the target showing/hiding.
- final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
- return new AlphaAnimation(alpha, alpha);
- }
-
- /** Animation for target that is opening in a change transition. */
- @NonNull
- Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- final Rect bounds = target.screenSpaceBounds;
- final int startLeft;
- final int startTop;
- if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated in from left or right depending on its position.
- startTop = 0;
- startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
- } else {
- // The window will be animated in from top or bottom depending on its position.
- startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
- startLeft = 0;
- }
-
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
- animation.setInterpolator(mFastOutExtraSlowInInterpolator);
- animation.setDuration(CHANGE_ANIMATION_DURATION);
- animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- /** Animation for target that is closing in a change transition. */
- @NonNull
- Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- // Use startBounds if the window is closing in case it may also resize.
- final Rect bounds = target.startBounds;
- final int endTop;
- final int endLeft;
- if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated out to left or right depending on its position.
- endTop = 0;
- endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
- } else {
- // The window will be animated out to top or bottom depending on its position.
- endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
- endLeft = 0;
- }
-
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
- animation.setInterpolator(mFastOutExtraSlowInInterpolator);
- animation.setDuration(CHANGE_ANIMATION_DURATION);
- animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- /**
- * Animation for target that is changing (bounds change) in a change transition.
- * @return the return array always has two elements. The first one is for the start leash, and
- * the second one is for the end leash.
- */
- @NonNull
- Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
- // Both start bounds and end bounds are in screen coordinates. We will post translate
- // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
- final Rect startBounds = target.startBounds;
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- final Rect endBounds = target.screenSpaceBounds;
- float scaleX = ((float) startBounds.width()) / endBounds.width();
- float scaleY = ((float) startBounds.height()) / endBounds.height();
- // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
- // be scaled up with its parent.
- float startScaleX = 1.f / scaleX;
- float startScaleY = 1.f / scaleY;
-
- // The start leash will be fade out.
- final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
- final Animation startAlpha = new AlphaAnimation(1f, 0f);
- startAlpha.setInterpolator(mLinearInterpolator);
- startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
- startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
- startSet.addAnimation(startAlpha);
- final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
- startScaleY);
- startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
- startScale.setDuration(CHANGE_ANIMATION_DURATION);
- startSet.addAnimation(startScale);
- startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
- endBounds.height());
- startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
- // The end leash will be moved into the end position while scaling.
- final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
- endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
- final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
- endScale.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(endScale);
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- startBounds.top - endBounds.top, 0);
- endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(endTranslate);
- // The end leash is resizing, we should update the window crop based on the clip rect.
- final Rect startClip = new Rect(startBounds);
- final Rect endClip = new Rect(endBounds);
- startClip.offsetTo(0, 0);
- endClip.offsetTo(0, 0);
- final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
- clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(clipAnim);
- endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
- parentBounds.height());
- endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
- return new Animation[]{startSet, endSet};
- }
-
- @NonNull
- Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
- @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation;
- // Background color on TaskDisplayArea has already been set earlier in
- // WindowContainer#getAnimationAdapter.
- if (target.showBackdrop) {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
- : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
- } else {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_open_enter
- : com.android.internal.R.anim.task_fragment_open_exit);
- }
- // Use the whole animation bounds instead of the change bounds, so that when multiple change
- // targets are opening at the same time, the animation applied to each will be the same.
- // Otherwise, we may see gap between the activities that are launching together.
- animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
- wholeAnimationBounds.width(), wholeAnimationBounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- @NonNull
- Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
- @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation;
- if (target.showBackdrop) {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
- : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
- } else {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_close_enter
- : com.android.internal.R.anim.task_fragment_close_exit);
- }
- // Use the whole animation bounds instead of the change bounds, so that when multiple change
- // targets are closing at the same time, the animation applied to each will be the same.
- // Otherwise, we may see gap between the activities that are finishing together.
- animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
- wholeAnimationBounds.width(), wholeAnimationBounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- private float getTransitionAnimationScaleSetting() {
- return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault)));
- }
-
- private class SettingsObserver extends ContentObserver {
- SettingsObserver(@NonNull Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- }
- }
-}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 7b473b0..ad41b18 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -23,8 +23,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -86,24 +84,6 @@
}
@Test
- public void testUnregisterOrganizer() {
- mOrganizer.overrideSplitAnimation();
- mOrganizer.unregisterOrganizer();
-
- verify(mOrganizer).unregisterRemoteAnimations();
- }
-
- @Test
- public void testOverrideSplitAnimation() {
- assertNull(mOrganizer.mAnimationController);
-
- mOrganizer.overrideSplitAnimation();
-
- assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index efeec82..d852204 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -103,8 +103,6 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
-import com.android.window.flags.Flags;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -167,6 +165,7 @@
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
+ private ActivityThread mCurrentActivityThread;
@Before
public void setUp() {
@@ -183,10 +182,12 @@
};
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
+ mCurrentActivityThread = ActivityThread.currentActivityThread();
spyOn(mSplitController);
spyOn(mSplitPresenter);
spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
+ spyOn(mCurrentActivityThread);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -1557,8 +1558,6 @@
@Test
public void testIsActivityEmbedded() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
assertFalse(mSplitController.isActivityEmbedded(mActivity));
doReturn(true).when(mActivityWindowInfo).isEmbedded();
@@ -1568,8 +1567,6 @@
@Test
public void testGetEmbeddedActivityWindowInfo() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final boolean isEmbedded = true;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
@@ -1584,8 +1581,6 @@
@Test
public void testSetEmbeddedActivityWindowInfoCallback() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final ClientTransactionListenerController controller = ClientTransactionListenerController
.getInstance();
spyOn(controller);
@@ -1676,7 +1671,8 @@
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
+ doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient(
+ activityToken);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
deleted file mode 100644
index a1e9f08..0000000
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.Mockito.never;
-
-import android.platform.test.annotations.Presubmit;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Test class for {@link TaskFragmentAnimationController}.
- *
- * Build/Install/Run:
- * atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaskFragmentAnimationControllerTest {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- @Mock
- private TaskFragmentOrganizer mOrganizer;
- private TaskFragmentAnimationController mAnimationController;
-
- @Before
- public void setup() {
- mAnimationController = new TaskFragmentAnimationController(mOrganizer);
- }
-
- @Test
- public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations();
-
- verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
-
- mAnimationController.registerRemoteAnimations();
-
- // No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- }
-
- @Test
- public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations();
-
- // No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations();
-
- mAnimationController.registerRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations();
-
- mAnimationController.unregisterRemoteAnimations();
-
- // No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations();
- }
-}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index dbcad8a..a00d003 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -224,7 +224,6 @@
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
- "com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
"WindowManager-Shell-shared",
"perfetto_trace_java_protos",
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 3ff40e0..56ea7c2 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -113,16 +113,6 @@
}
flag {
- name: "animate_bubble_size_change"
- namespace: "multitasking"
- description: "Turns on the animation for bubble bar icons size change"
- bug: "335575529"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_taskbar_on_phones"
namespace: "multitasking"
description: "Enables taskbar on phones"
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 419d5c0..864f7cd 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
@@ -19,6 +19,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/desktop_mode_handle_menu_width"
android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:paddingRight="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="vertical">
<LinearLayout
@@ -27,7 +31,7 @@
android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
android:layout_marginStart="1dp"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="horizontal"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -73,7 +77,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="horizontal"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -124,7 +128,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
@@ -143,7 +147,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d143263..269a586 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -495,9 +495,16 @@
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
- <!-- The width of the handle menu in desktop mode. -->
+ <!-- The width of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
+ spacing between them plus 4dp top padding. -->
+ <dimen name="desktop_mode_handle_menu_height">218dp</dimen>
+
+ <!-- The elevation set on the handle menu pills. -->
+ <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+
<!-- The height of the handle menu's "App Info" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
@@ -510,8 +517,8 @@
<!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
- <!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
+ <!-- The margin between pills of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -519,9 +526,6 @@
<!-- The start margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
- <!-- The margin between pills of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
-
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ece0271..8467e97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -422,6 +422,12 @@
@VisibleForTesting
public void onThresholdCrossed() {
mThresholdCrossed = true;
+ // There was no focus window when calling startBackNavigation, still pilfer pointers so
+ // the next focus window won't receive motion events.
+ if (mBackNavigationInfo == null) {
+ tryPilferPointers();
+ return;
+ }
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
@@ -542,6 +548,7 @@
if (backNavigationInfo == null) {
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
cancelLatencyTracking();
+ tryPilferPointers();
return;
}
final int backType = backNavigationInfo.getType();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index bfee820..736d954 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -54,4 +54,11 @@
public boolean hasCallback(Runnable r) {
return mHandler.hasCallbacks(r);
}
+
+ @Override
+ public void assertCurrentThread() {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("must be called on " + mHandler);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index f729164..2c2961f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -96,4 +96,11 @@
* See {@link android.os.Handler#hasCallbacks(Runnable)}.
*/
boolean hasCallback(Runnable runnable);
+
+ /**
+ * May throw if the caller is not on the same thread as the executor.
+ */
+ default void assertCurrentThread() {
+ return;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
new file mode 100644
index 0000000..cb54d89
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.api
+
+/**
+ * Abstraction for the repository of all the available CompatUISpec
+ */
+interface CompatUIRepository {
+ /**
+ * Adds a {@link CompatUISpec} to the repository
+ * @throws IllegalStateException in case of illegal spec
+ */
+ fun addSpec(spec: CompatUISpec)
+
+ /**
+ * Iterates on the list of available {@link CompatUISpec} invoking
+ * fn for each of them.
+ */
+ fun iterateOn(fn: (CompatUISpec) -> Unit)
+
+ /**
+ * Returns the {@link CompatUISpec} for a given key
+ */
+ fun findSpec(name: String): CompatUISpec?
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
index c92234c..24c2c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.wm.shell.compatui.api
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+/**
+ * Describes each compat ui component to the framework.
+ */
+data class CompatUISpec(
+ // Unique name for the component. It's used for debug and for generating the
+ // unique component identifier in the system.
+ val name: String
+)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index a181eaf..8408ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -19,12 +19,15 @@
import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIRepository
import java.util.function.Consumer
/**
* Default implementation of {@link CompatUIHandler} to handle CompatUI components
*/
-class DefaultCompatUIHandler : CompatUIHandler {
+class DefaultCompatUIHandler(
+ private val compatUIRepository: CompatUIRepository
+) : CompatUIHandler {
private var compatUIEventSender: Consumer<CompatUIEvent>? = null
override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
new file mode 100644
index 0000000..10d9425
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Default {@link CompatUIRepository} implementation
+ */
+class DefaultCompatUIRepository : CompatUIRepository {
+
+ private val allSpecs = mutableMapOf<String, CompatUISpec>()
+
+ override fun addSpec(spec: CompatUISpec) {
+ if (allSpecs[spec.name] != null) {
+ throw IllegalStateException("Spec with id:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 9bdc0b2..4b548cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,9 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -245,12 +247,13 @@
Lazy<DockStateReader> dockStateReader,
Lazy<CompatUIConfiguration> compatUIConfiguration,
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
- Lazy<AccessibilityManager> accessibilityManager) {
+ Lazy<AccessibilityManager> accessibilityManager,
+ CompatUIRepository compatUIRepository) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
if (Flags.appCompatUiFramework()) {
- return Optional.of(new DefaultCompatUIHandler());
+ return Optional.of(new DefaultCompatUIHandler(compatUIRepository));
}
return Optional.of(
new CompatUIController(
@@ -271,6 +274,12 @@
@WMSingleton
@Provides
+ static CompatUIRepository provideCompatUIRepository() {
+ return new DefaultCompatUIRepository();
+ }
+
+ @WMSingleton
+ @Provides
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
@ShellMainThread ShellExecutor mainExecutor) {
return new SyncTransactionQueue(pool, mainExecutor);
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 eeceaa9..45feff5 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
@@ -245,7 +245,10 @@
return new CaptionWindowDecorViewModel(
context,
mainHandler,
+ mainExecutor,
mainChoreographer,
+ windowManager,
+ shellInit,
taskOrganizer,
displayController,
rootTaskDisplayAreaOrganizer,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 240cf3b..1a9c304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -38,12 +38,10 @@
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -79,7 +77,7 @@
public abstract class Pip1Module {
@WMSingleton
@Provides
- static Optional<Pip> providePip1(Context context,
+ static Optional<PipController.PipImpl> providePip1(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -104,20 +102,16 @@
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- if (PipUtils.isPip2ExperimentEnabled()) {
- return Optional.empty();
- } else {
- return Optional.ofNullable(PipController.create(
- context, shellInit, shellCommandHandler, shellController,
- displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController,
- mainExecutor));
- }
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController,
+ mainExecutor));
}
// Handler is used by Icon.loadDrawableAsync
@@ -212,13 +206,12 @@
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 6968317..ea7e968 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -81,6 +81,16 @@
@WMSingleton
@Provides
+ static Optional<PipController.PipImpl> providePip2(Optional<PipController> pipController) {
+ if (pipController.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(pipController.get().getPipImpl());
+ }
+ }
+
+ @WMSingleton
+ @Provides
static Optional<PipController> providePipController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index f2631ef..a3afe78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -18,12 +18,16 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies for external components / modules reference PiP and extracts away the
* selection of legacy and new PiP implementation.
@@ -44,4 +48,17 @@
return legacyPipTransition;
}
}
+
+ @WMSingleton
+ @Provides
+ static Optional<Pip> providePip(
+ Optional<com.android.wm.shell.pip.phone.PipController.PipImpl> pip1,
+ Optional<PipController.PipImpl> pip2) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.ofNullable(pip2.orElse(null));
+
+ } else {
+ return Optional.ofNullable(pip1.orElse(null));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index ca05864..247cc42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -88,14 +88,17 @@
/** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- displayData.keyIterator().forEach { displayId ->
- val visibleTasksCount = getVisibleTaskCount(displayId)
+ displayData.keyIterator().forEach {
executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
}
}
}
+ /** Returns a list of all [DisplayData]. */
+ private fun displayDataList(): Sequence<DisplayData> =
+ displayData.valueIterator().asSequence()
+
/**
* Add a Consumer which will inform other classes of changes to exclusion regions for all
* Desktop tasks.
@@ -208,37 +211,17 @@
return removed
}
- /** Check if a task with the given [taskId] was marked as an active task */
- fun isActiveTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.activeTasks.contains(taskId)
- }
- }
-
- /** Check if a task with the given [taskId] was marked as a closing task */
- fun isClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }
-
- /** Whether a task is visible. */
- fun isVisibleTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.visibleTasks.contains(taskId)
- }
- }
-
- /** Return whether the given Task is minimized. */
- fun isMinimizedTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.minimizedTasks.contains(taskId)
- }
- }
+ fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
+ fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
+ fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
+ fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
/**
* Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
* on its display
*/
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data ->
+ displayDataList().any { data ->
data.visibleTasks
.subtract(data.closingTasks)
.subtract(data.minimizedTasks)
@@ -255,12 +238,6 @@
ArraySet(displayData[displayId]?.minimizedTasks)
/**
- * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
- * are visible.
- */
- fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0
-
- /**
* Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
* ordered from front to back.
*/
@@ -305,14 +282,14 @@
return
}
- val prevCount = getVisibleTaskCount(displayId)
+ val prevCount = visibleTaskCount(displayId)
if (visible) {
displayData.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
displayData[displayId]?.visibleTasks?.remove(taskId)
}
- val newCount = getVisibleTaskCount(displayId)
+ val newCount = visibleTaskCount(displayId)
// Check if count changed
if (prevCount != newCount) {
@@ -340,7 +317,7 @@
}
/** Get number of tasks that are marked as visible on given [displayId] */
- fun getVisibleTaskCount(displayId: Int): Int {
+ fun visibleTaskCount(displayId: Int): Int {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
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 18157d6..9a1a8a2 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
@@ -159,18 +159,6 @@
}
}
- private val transitionAreaHeight
- get() =
- context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
- )
-
- private val transitionAreaWidth
- get() =
- context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
- )
-
/** Task id of the task currently being dragged from fullscreen/split. */
val draggingTaskId
get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -229,6 +217,15 @@
dragToDesktopTransitionHandler.setSplitScreenController(controller)
}
+ /** Returns the transition type for the given remote transition. */
+ private fun transitionType(remoteTransition: RemoteTransition?): Int {
+ if (remoteTransition == null) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: remoteTransition is null")
+ return TRANSIT_NONE
+ }
+ return TRANSIT_TO_FRONT
+ }
+
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
@@ -236,8 +233,7 @@
bringDesktopAppsToFront(displayId, wct)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/309014605): ensure remote transition is supplied once state is introduced
- val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+ val transitionType = transitionType(remoteTransition)
val handler =
remoteTransition?.let {
OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -250,10 +246,12 @@
}
}
- /** Get number of tasks that are marked as visible */
- fun getVisibleTaskCount(displayId: Int): Int {
- return desktopModeTaskRepository.getVisibleTaskCount(displayId)
- }
+ /** Gets number of visible tasks in [displayId]. */
+ fun visibleTaskCount(displayId: Int): Int =
+ desktopModeTaskRepository.visibleTaskCount(displayId)
+
+ /** Returns true if any tasks are visible in Desktop Mode. */
+ fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
/** Enter desktop by using the focused task in given `displayId` */
fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -776,12 +774,13 @@
newTaskIdInFront ?: "null"
)
- if (Flags.enableDesktopWindowingWallpaperActivity()) {
+ // Move home to front, ensures that we go back home when all desktop windows are closed
+ moveHomeTask(wct, toTop = true)
+
+ // Currently, we only handle the desktop on the default display really.
+ if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
// Add translucent wallpaper activity to show the wallpaper underneath
addWallpaperActivity(wct)
- } else {
- // Move home to front
- moveHomeTask(wct, toTop = true)
}
val nonMinimizedTasksOrderedFrontToBack =
@@ -984,7 +983,7 @@
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
return null
}
- if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (!isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bring desktop tasks to front on transition" +
@@ -1015,7 +1014,7 @@
transition: IBinder
): WindowContainerTransaction? {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
- if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
@@ -1048,14 +1047,12 @@
/** Handle task closing by removing wallpaper activity if it's the last active task */
private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
- val wct = if (
- desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) &&
- desktopModeTaskRepository.wallpaperActivityToken != null
- ) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleTaskClosing")
+ val wct = WindowContainerTransaction()
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ && desktopModeTaskRepository.wallpaperActivityToken != null) {
// Remove wallpaper activity when the last active task is removed
- WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
- } else {
- null
+ removeWallpaperActivity(wct)
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
@@ -1065,7 +1062,12 @@
task.taskId
)
}
- return wct
+ // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
+ if (Flags.enableDesktopWindowingBackNavigation() &&
+ desktopModeTaskRepository.isVisibleTask(task.taskId)) {
+ wct.removeTask(task.token)
+ }
+ return if (wct.isEmpty) null else wct
}
private fun addMoveToDesktopChanges(
@@ -1110,6 +1112,10 @@
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/**
@@ -1125,6 +1131,10 @@
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
@@ -1388,8 +1398,7 @@
onFinishCallback: Consumer<Boolean>
): Boolean {
// TODO(b/320797628): Pass through which display we are dropping onto
- val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
- if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
// Not currently in desktop mode, ignore the drop
return false
}
@@ -1548,8 +1557,8 @@
val result = IntArray(1)
executeRemoteCallWithTaskPermission(
controller,
- "getVisibleTaskCount",
- { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
+ "visibleTaskCount",
+ { controller -> result[0] = controller.visibleTaskCount(displayId) },
true /* blocking */
)
return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 3572d16..84f6af41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -68,7 +68,7 @@
## Tracing global SurfaceControl transaction updates
While Winscope traces are very useful, it sometimes doesn't give you enough information about which
-part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
stack traces when specific surface transaction calls are made, which is possible by enabling the
following system properties for example:
```shell
@@ -81,9 +81,11 @@
# Disabling logging
adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
-adb reboot
```
+A reboot is required to enable the logging. Once enabled, reboot is not needed to update the
+properties.
+
It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
noisy if unfiltered.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index e0b0866..e00353d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -302,7 +302,7 @@
break;
}
}
- if (pd == null || !pd.isHandlingDrag) {
+ if (pd == null || pd.activeDragCount <= 0 || !pd.isHandlingDrag) {
// Not currently dragging
return;
}
@@ -333,9 +333,10 @@
mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
+ "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
pd.isHandlingDrag, event.getClipData().getItemCount(),
- DragUtils.getMimeTypesConcatenated(description));
+ DragUtils.getMimeTypesConcatenated(description),
+ DragUtils.dragFlagsToString(event.getDragFlags()));
}
if (!pd.isHandlingDrag) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index f0514e3..e4aa115 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -293,6 +293,8 @@
int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
+ mDropZoneView1.setForceIgnoreBottomMargin(false);
+ mDropZoneView2.setForceIgnoreBottomMargin(false);
updateDropZoneSizes(null, null); // passing null splits the views evenly
} else {
// We use the first drop zone to show the fullscreen highlight, and don't need
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index e215870..22cfa32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -19,16 +19,28 @@
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.view.View.DRAG_FLAG_ACCESSIBILITY_ACTION;
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_READ;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_WRITE;
+import static android.view.View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START;
+import static android.view.View.DRAG_FLAG_OPAQUE;
+import static android.view.View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION;
+import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.StringJoiner;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -76,7 +88,7 @@
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) {
- if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
+ if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
// Disallow launching the intent if the app does not want to delegate it to the system
return null;
}
@@ -105,4 +117,35 @@
}
return mimeTypes;
}
+
+ /**
+ * Returns the string description of the given {@param dragFlags}.
+ */
+ public static String dragFlagsToString(int dragFlags) {
+ StringJoiner str = new StringJoiner("|");
+ if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) {
+ str.add("GLOBAL");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
+ str.add("GLOBAL_URI_READ");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
+ str.add("GLOBAL_URI_WRITE");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PERSISTABLE_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PREFIX_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
+ str.add("OPAQUE");
+ } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ str.add("ACCESSIBILITY_ACTION");
+ } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
+ str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
+ str.add("GLOBAL_SAME_APPLICATION");
+ } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG");
+ } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ str.add("HIDE_CALLING_TASK_ON_DRAG_START");
+ }
+ return str.toString();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index b6b49a8..18cd2d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -151,6 +151,10 @@
/** Ignores the bottom margin provided by the insets. */
public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "setForceIgnoreBottomMargin: ignore=%b", ignoreBottomMargin);
+ }
mIgnoreBottomMargin = ignoreBottomMargin;
if (mMarginPercent > 0) {
mMarginView.invalidate();
@@ -159,8 +163,14 @@
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setBottomInset: inset=%f",
+ bottom);
+ }
mBottomInset = bottom;
- ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
+ final LayoutParams lp = (LayoutParams) mSplashScreenView.getLayoutParams();
+ lp.bottomMargin = (int) bottom;
+ mSplashScreenView.setLayoutParams(lp);
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,12 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -71,10 +69,9 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
- * started / finished callbacks.
+ * @return {@link PipTransitionController} instance.
*/
- default void registerPipTransitionCallback(
- @NonNull PipTransitionController.PipTransitionCallback callback,
- @NonNull Executor executor) { }
+ default PipTransitionController getPipTransitionController() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6e1e44b..ff40d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,8 +423,7 @@
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(
- mPipTransitionCallback, mMainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
}
}
@@ -496,9 +495,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- if (!ENABLE_SHELL_TRANSITIONS) {
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
- }
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87692ac..e5633de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1174,7 +1174,6 @@
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 8d36db9..b1dd4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,9 +53,8 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
/**
* Responsible supplying PiP Transitions.
@@ -67,7 +66,7 @@
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
+ private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -184,18 +183,16 @@
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(
- @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
- mPipTransitionCallbacks.put(callback, executor);
+ public void registerPipTransitionCallback(PipTransitionCallback callback) {
+ mPipTransitionCallbacks.add(callback);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionStarted(direction, pipBounds);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -212,10 +209,9 @@
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionFinished(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionFinished(direction);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -232,10 +228,9 @@
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionCanceled(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionCanceled(direction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d1d8275..26b7e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,7 +106,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -374,7 +373,7 @@
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
- public static Pip create(Context context,
+ public static PipImpl create(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -479,7 +478,7 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1177,7 +1176,7 @@
/**
* The interface for calls from outside the Shell, within the host process.
*/
- private class PipImpl implements Pip {
+ public class PipImpl implements Pip {
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1221,11 +1220,8 @@
}
@Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {
- mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
- callback, executor));
+ public PipTransitionController getPipTransitionController() {
+ return mPipTransitionController;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..e8d6576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,7 +38,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,7 +47,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -173,9 +171,7 @@
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
- @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -187,7 +183,7 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..62c0944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 06adad6..b939b16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -55,6 +55,8 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -62,6 +64,7 @@
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
@@ -86,6 +89,8 @@
private final ShellTaskOrganizer mShellTaskOrganizer;
private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
+ private final PipImpl mImpl;
+ private Consumer<Boolean> mOnIsInPipStateChangedListener;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
private PipAnimationListener mPipRecentsAnimationListener;
@@ -140,6 +145,7 @@
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
+ mImpl = new PipImpl();
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
@@ -174,6 +180,10 @@
pipTransitionState, mainExecutor);
}
+ public PipImpl getPipImpl() {
+ return mImpl;
+ }
+
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
// Ensure that we have the display info in case we get calls to update the bounds before the
@@ -310,22 +320,29 @@
@Override
public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
- if (newState == PipTransitionState.SWIPING_TO_PIP) {
- Preconditions.checkState(extra != null,
- "No extra bundle for " + mPipTransitionState);
+ switch (newState) {
+ case PipTransitionState.SWIPING_TO_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
- SurfaceControl overlay = extra.getParcelable(
- SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
- Rect appBounds = extra.getParcelable(
- SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
- Preconditions.checkState(appBounds != null,
- "App bounds can't be null for " + mPipTransitionState);
- mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
- } else if (newState == PipTransitionState.ENTERED_PIP) {
- if (mPipTransitionState.isInSwipePipToHomeTransition()) {
- mPipTransitionState.resetSwipePipToHomeState();
- }
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ break;
+ case PipTransitionState.ENTERED_PIP:
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ break;
}
}
@@ -355,6 +372,48 @@
mPipTransitionState.dump(pw, innerPrefix);
}
+ private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mOnIsInPipStateChangedListener = callback;
+ if (mOnIsInPipStateChangedListener != null) {
+ callback.accept(mPipTransitionState.isInPip());
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ public class PipImpl implements Pip {
+ @Override
+ public void expandPip() {}
+
+ @Override
+ public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
+
+ @Override
+ public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.setOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.removePipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void showPictureInPictureMenu() {}
+ }
+
/**
* The interface for calls from outside the host process.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index e277a8d..ea02de9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -816,6 +816,26 @@
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
+
+ // Check whether new bounds after fling imply we need to update stash state too.
+ stashEndActionIfNeeded();
+ }
+
+ private void stashEndActionIfNeeded() {
+ boolean isStashing = mPipBoundsState.getBounds().right > mPipBoundsState
+ .getDisplayBounds().width() || mPipBoundsState.getBounds().left < 0;
+ if (!isStashing) {
+ return;
+ }
+
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 0c4ed26..53b80e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -106,9 +106,6 @@
private float mStashVelocityThreshold;
- // The reference inset bounds, used to determine the dismiss fraction
- private final Rect mInsetBounds = new Rect();
-
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds
private int mDeferResizeToNormalBoundsUntilRotation = -1;
@@ -206,17 +203,10 @@
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
- if (mPipBoundsState.isStashed()) {
- animateToUnStashedState();
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
- } else {
- mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
- mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
- willResizeMenu(),
- shouldShowResizeHandle());
- }
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
},
menuController::hideMenu,
mainExecutor);
@@ -438,7 +428,6 @@
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
mDisplayRotation = displayRotation;
- mInsetBounds.set(insetBounds);
updateMovementBounds();
mMovementBoundsExtraOffsets = extraOffset;
@@ -748,10 +737,13 @@
final Rect pipBounds = mPipBoundsState.getBounds();
final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
- unStashedBounds.left = onLeftEdge ? mInsetBounds.left
- : mInsetBounds.right - pipBounds.width();
- unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
- : mInsetBounds.right;
+
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ unStashedBounds.left = onLeftEdge ? insetBounds.left
+ : insetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? insetBounds.left + pipBounds.width()
+ : insetBounds.right;
mMotionHelper.animateToUnStashedBounds(unStashedBounds);
}
@@ -899,8 +891,7 @@
// Reset the touch state on up before the fling settles
mTouchState.reset();
if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
- // mMotionHelper.stashToEdge(vel.x, vel.y,
- // this::stashEndAction /* endAction */);
+ mMotionHelper.stashToEdge(vel.x, vel.y, null /* endAction */);
} else {
if (mPipBoundsState.isStashed()) {
// Reset stashed state if previously stashed
@@ -1030,8 +1021,10 @@
* resized.
*/
private void updateMovementBounds() {
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
- mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index a126cbe..9750d3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -535,7 +535,8 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */,
+ 0 /* flags */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4f4b809..766a6b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -353,7 +353,7 @@
return this::setRecentsTransitionDuringKeyguard;
} else if (mDesktopTasksController != null
// Check on the default display. Recents/gesture nav is only available there
- && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ && mDesktopTasksController.visibleTaskCount(DEFAULT_DISPLAY) > 0) {
return this::setRecentsTransitionDuringDesktop;
}
}
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 73b32a2..7784784 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
@@ -19,6 +19,7 @@
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -40,6 +41,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -62,6 +64,7 @@
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.isCoveredByOpaqueFullscreenChange;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
@@ -352,6 +355,7 @@
continue;
}
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
final int mode = change.getMode();
boolean isSeamlessDisplayChange = false;
@@ -458,6 +462,16 @@
final int layer = zSplitLine + numChanges - i;
startTransaction.setLayer(change.getLeash(), layer);
}
+ } else if (!isCoveredByOpaqueFullscreenChange(info, change)
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Reparent the minimize-change to the root task so the minimizing Task
+ // isn't shown in front of other Tasks.
+ mRootTDAOrganizer.reparentToDisplayArea(
+ change.getTaskInfo().displayId,
+ change.getLeash(),
+ startTransaction);
} else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
&& TransitionUtil.isClosingType(mode)) {
// If there is a closing translucent task in an OPENING transition, we will
@@ -985,7 +999,8 @@
final int animType = options.getType();
return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
|| animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
- || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
+ || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS
+ || animType == ANIM_FROM_STYLE;
}
private static void applyTransformation(long time, SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a5f071a..75e7ddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -36,6 +36,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.WindowConfiguration;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -72,6 +73,9 @@
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
+ final boolean isCoveredByOpaqueFullscreenChange =
+ isCoveredByOpaqueFullscreenChange(info, change);
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
@@ -107,6 +111,24 @@
animAttr = enter
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // The main Task is launching or being brought to front, this Task is being minimized
+ animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && type == TRANSIT_TO_FRONT
+ && change.getMode() == TRANSIT_TO_FRONT) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // Bring the minimized Task back to front
+ animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation;
} else if (type == TRANSIT_OPEN) {
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
@@ -417,4 +439,25 @@
return edgeExtensionLayer;
}
+
+ /**
+ * Returns whether there is an opaque fullscreen Change positioned in front of the given Change
+ * in the given TransitionInfo.
+ */
+ static boolean isCoveredByOpaqueFullscreenChange(
+ TransitionInfo info, TransitionInfo.Change change) {
+ // TransitionInfo#getChanges() are ordered from front to back
+ for (TransitionInfo.Change coveringChange : info.getChanges()) {
+ if (coveringChange == change) {
+ return false;
+ }
+ if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0
+ && coveringChange.getTaskInfo() != null
+ && coveringChange.getTaskInfo().getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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 21307a2..8d53beb 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
@@ -31,12 +31,14 @@
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.window.flags.Flags.enforceShellThreadModel;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
@@ -806,14 +808,16 @@
final int changeSize = info.getChanges().size();
boolean taskChange = false;
boolean transferStartingWindow = false;
- int noAnimationBehindStartingWindow = 0;
+ int animBehindStartingWindow = 0;
boolean allOccluded = changeSize > 0;
for (int i = changeSize - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
taskChange |= change.getTaskInfo() != null;
transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
- if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
- noAnimationBehindStartingWindow++;
+ if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)
+ || change.hasAllFlags(
+ FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ animBehindStartingWindow++;
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
@@ -831,11 +835,11 @@
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
// task change. Since this is an activity->activity situation, we can detect it by selecting
- // transitions with only 2 changes where
- // 1. neither are tasks, and
+ // transitions with changes where
+ // 1. none are tasks, and
// 2. one is a starting-window recipient, or all change is behind starting window.
- if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
- && changeSize == 2
+ if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize)
+ && changeSize >= 1
// B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -921,9 +925,12 @@
}
// An existing animation is playing, so see if we can merge.
final ActiveTransition playing = track.mActiveTransition;
+ final IBinder playingToken = playing.mToken;
+ final IBinder readyToken = ready.mToken;
+
if (ready.mAborted) {
// record as merged since it is no-op. Calls back into processReadyQueue
- onMerged(playing, ready);
+ onMerged(playingToken, readyToken);
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
@@ -931,14 +938,31 @@
+ " in case they can be merged", ready, playing);
mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
- playing.mToken, (wct) -> onMerged(playing, ready));
+ playing.mToken, (wct) -> onMerged(playingToken, readyToken));
}
- private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ private void onMerged(@NonNull IBinder playingToken, @NonNull IBinder mergedToken) {
+ if (enforceShellThreadModel()) {
+ mMainExecutor.assertCurrentThread();
+ }
+
+ ActiveTransition playing = mKnownTransitions.get(playingToken);
+ if (playing == null) {
+ Log.e(TAG, "Merging into a non-existent transition: " + playingToken);
+ return;
+ }
+
+ ActiveTransition merged = mKnownTransitions.get(mergedToken);
+ if (merged == null) {
+ Log.e(TAG, "Merging a non-existent transition: " + mergedToken);
+ return;
+ }
+
if (playing.getTrack() != merged.getTrack()) {
throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
+ playing);
}
+
final Track track = mTracks.get(playing.getTrack());
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
merged, playing);
@@ -1068,13 +1092,17 @@
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder token,
- @Nullable WindowContainerTransaction wct) {
+ private void onFinish(IBinder token, @Nullable WindowContainerTransaction wct) {
+ if (enforceShellThreadModel()) {
+ mMainExecutor.assertCurrentThread();
+ }
+
final ActiveTransition active = mKnownTransitions.get(token);
if (active == null) {
Log.e(TAG, "Trying to finish a non-existent transition: " + token);
return;
}
+
final Track track = mTracks.get(active.getTrack());
if (track == null || track.mActiveTransition != active) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 95e0d79..b9cb6d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,12 +26,20 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ContentResolver;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.provider.Settings;
+import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.Display;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -45,39 +53,74 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+ private static final String TAG = "CaptionWindowDecorViewModel";
+
private final ShellTaskOrganizer mTaskOrganizer;
+ private final IWindowManager mWindowManager;
private final Context mContext;
private final Handler mMainHandler;
+ private final ShellExecutor mMainExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Transitions mTransitions;
+ private final Region mExclusionRegion = Region.obtain();
+ private final InputManager mInputManager;
private TaskOperations mTaskOperations;
+ /**
+ * Whether to pilfer the next motion event to send cancellations to the windows below.
+ * Useful when the caption window is spy and the gesture should be handled by the system
+ * instead of by the app for their custom header content.
+ */
+ private boolean mShouldPilferCaptionEvents;
+
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+ private final ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ mMainExecutor.execute(() -> {
+ mExclusionRegion.set(systemGestureExclusion);
+ });
+ }
+ };
+
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
+ ShellExecutor shellExecutor,
Choreographer mainChoreographer,
+ IWindowManager windowManager,
+ ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions) {
mContext = context;
+ mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
+ mWindowManager = windowManager;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
@@ -87,6 +130,18 @@
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
+ mInputManager = mContext.getSystemService(InputManager.class);
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ try {
+ mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
+ mContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register window manager callbacks", e);
+ }
}
@Override
@@ -178,8 +233,12 @@
}
private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
+ if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ decoration.setCaptionColor(Color.TRANSPARENT);
+ } else {
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
@@ -301,6 +360,49 @@
mSyncQueue.queue(wct);
}
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+
+ final int actionMasked = e.getActionMasked();
+ final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+ final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
+ || actionMasked == MotionEvent.ACTION_UP;
+ if (isDown) {
+ final boolean downInCustomizableCaptionRegion =
+ decoration.checkTouchEventInCustomizableRegion(e);
+ final boolean downInExclusionRegion = mExclusionRegion.contains(
+ (int) e.getRawX(), (int) e.getRawY());
+ final boolean isTransparentCaption =
+ TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
+ // MotionEvent's coordinates are relative to view, we want location in window
+ // to offset position relative to caption as a whole.
+ int[] viewLocation = new int[2];
+ v.getLocationInWindow(viewLocation);
+ final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e,
+ new Point(viewLocation[0], viewLocation[1]));
+ // The caption window may be a spy window when the caption background is
+ // transparent, which means events will fall through to the app window. Make
+ // sure to cancel these events if they do not happen in the intersection of the
+ // customizable region and what the app reported as exclusion areas, because
+ // the drag-move or other caption gestures should take priority outside those
+ // regions.
+ mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
+ }
+
+ if (!mShouldPilferCaptionEvents) {
+ // The event will be handled by a window below or pilfered by resize handler.
+ return false;
+ }
+ // Otherwise pilfer so that windows below receive cancellations for this gesture, and
+ // continue normal handling as a caption gesture.
+ if (mInputManager != null) {
+ // TODO(b/352127475): Only pilfer once per gesture
+ mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ }
+ if (isUpOrCancel) {
+ // Gesture is finished, reset state.
+ mShouldPilferCaptionEvents = false;
+ }
return mDragDetector.onMotionEvent(e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index d0ca5b0..7e1b973 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -28,22 +29,27 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.util.Size;
import android.view.Choreographer;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowManager;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -177,12 +183,44 @@
shouldSetTaskPositionAndCrop);
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw,
+ boolean setTaskCropAndPosition) {
+ relayoutParams.reset();
+ relayoutParams.mRunningTaskInfo = taskInfo;
+ relayoutParams.mLayoutResId = R.layout.caption_window_decor;
+ relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+ relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+
+ if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ // If the app is requesting to customize the caption bar, allow input to fall
+ // through to the windows below so that the app can respond to input events on
+ // their custom content.
+ relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
+ final RelayoutParams.OccludingCaptionElement backButtonElement =
+ new RelayoutParams.OccludingCaptionElement();
+ backButtonElement.mWidthResId = R.dimen.caption_left_buttons_width;
+ backButtonElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
+ relayoutParams.mOccludingCaptionElements.add(backButtonElement);
+ // Then, the right-aligned section (minimize, maximize and close buttons).
+ final RelayoutParams.OccludingCaptionElement controlsElement =
+ new RelayoutParams.OccludingCaptionElement();
+ controlsElement.mWidthResId = R.dimen.caption_right_buttons_width;
+ controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
+ relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ }
+
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
- final int shadowRadiusID = taskInfo.isFocused
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
@@ -191,13 +229,8 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mRelayoutParams.reset();
- mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
- mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+ updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
+ setTaskCropAndPosition);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -303,6 +336,17 @@
mDragResizeListener = null;
}
+ /**
+ * Checks whether the touch event falls inside the customizable caption region.
+ */
+ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
+ return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
+ }
+
+ boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+ return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
+ }
+
@Override
public void close() {
closeDragResizeListener();
@@ -311,6 +355,10 @@
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return getCaptionHeightIdStatic(windowingMode);
+ }
+
+ private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index faf6a62..d12b906 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1040,7 +1040,7 @@
private void createInputChannel(int displayId) {
final InputManager inputManager = mContext.getSystemService(InputManager.class);
final InputMonitor inputMonitor =
- mInputMonitorFactory.create(inputManager, mContext);
+ mInputMonitorFactory.create(inputManager, displayId);
final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
@@ -1194,8 +1194,8 @@
}
static class InputMonitorFactory {
- InputMonitor create(InputManager inputManager, Context context) {
- return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ InputMonitor create(InputManager inputManager, int displayId) {
+ return inputManager.monitorGestureInput("caption-touch", displayId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5ffd883..5d662b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -852,18 +852,19 @@
*/
void createHandleMenu(SplitScreenController splitScreenController) {
loadAppInfoIfNeeded();
- mHandleMenu = new HandleMenu.Builder(this)
- .setAppIcon(mAppIconBitmap)
- .setAppName(mAppName)
- .setOnClickListener(mOnCaptionButtonClickListener)
- .setOnTouchListener(mOnCaptionTouchListener)
- .setLayoutId(mRelayoutParams.mLayoutResId)
- .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
- .setCaptionHeight(mResult.mCaptionHeight)
- .setDisplayController(mDisplayController)
- .setSplitScreenController(splitScreenController)
- .setBrowserLinkAvailable(browserLinkAvailable())
- .build();
+ mHandleMenu = new HandleMenu(
+ this,
+ mRelayoutParams.mLayoutResId,
+ mOnCaptionButtonClickListener,
+ mOnCaptionTouchListener,
+ mAppIconBitmap,
+ mAppName,
+ mDisplayController,
+ splitScreenController,
+ DesktopModeStatus.canEnterDesktopMode(mContext),
+ browserLinkAvailable(),
+ mResult.mCaptionHeight
+ );
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index a3616f6..32df8b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -468,8 +468,7 @@
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getDisplayId(), e.getDeviceId(),
- e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
- e.getYCursorPosition());
+ e.getPointerId(/*pointerIndex=*/0), e.getX(), e.getY());
result = true;
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index b5d1d4a..ba5f079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,9 +33,6 @@
import android.util.Size;
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
import com.android.wm.shell.R;
import java.util.Objects;
@@ -44,11 +41,6 @@
* Geometry for a drag resize region for a particular window.
*/
final class DragResizeWindowGeometry {
- // TODO(b/337264971) clean up when no longer needed
- @VisibleForTesting static final boolean DEBUG = true;
- // The additional width to apply to edge resize bounds just for logging when a touch is
- // close.
- @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
private final int mTaskCornerRadius;
private final Size mTaskSize;
// The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -60,8 +52,6 @@
private final @NonNull TaskCorners mFineTaskCorners;
// The bounds for each edge drag region, which can resize the task in one direction.
private final @NonNull TaskEdges mTaskEdges;
- // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
- private final @Nullable TaskEdges mDebugTaskEdges;
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -74,11 +64,6 @@
// Save touch areas for each edge.
mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
- if (DEBUG) {
- mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
- } else {
- mDebugTaskEdges = null;
- }
}
/**
@@ -120,13 +105,7 @@
*/
void union(@NonNull Region region) {
// Apply the edge resize regions.
- if (inDebugMode()) {
- // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
- // touch due to the size of the edge region.
- mDebugTaskEdges.union(region);
- } else {
- mTaskEdges.union(region);
- }
+ mTaskEdges.union(region);
if (enableWindowingEdgeDragResize()) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -219,10 +198,6 @@
@DragPositioningCallback.CtrlType
private int calculateEdgeResizeCtrlType(float x, float y) {
- if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
- && !mTaskEdges.contains((int) x, (int) y))) {
- return CTRL_TYPE_UNDEFINED;
- }
int ctrlType = CTRL_TYPE_UNDEFINED;
// mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
// sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -313,9 +288,7 @@
&& this.mResizeHandleThickness == other.mResizeHandleThickness
&& this.mFineTaskCorners.equals(other.mFineTaskCorners)
&& this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
- && (inDebugMode()
- ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
- : this.mTaskEdges.equals(other.mTaskEdges));
+ && this.mTaskEdges.equals(other.mTaskEdges);
}
@Override
@@ -326,11 +299,7 @@
mResizeHandleThickness,
mFineTaskCorners,
mLargeTaskCorners,
- (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
- }
-
- private boolean inDebugMode() {
- return DEBUG && mDebugTaskEdges != null;
+ mTaskEdges);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
deleted file mode 100644
index 7e44f32..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.windowdecor;
-
-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.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.window.SurfaceSyncGroup;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.window.flags.Flags;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
-
-/**
- * Handle menu opened when the appropriate button is clicked on.
- *
- * Displays up to 3 pills that show the following:
- * App Info: App name, app icon, and collapse button to close the menu.
- * Windowing Options(Proto 2 only): Buttons to change windowing modes.
- * Additional Options: Miscellaneous functions including screenshot and closing task.
- */
-class HandleMenu {
- private static final String TAG = "HandleMenu";
- private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
- private final Context mContext;
- private final DesktopModeWindowDecoration mParentDecor;
- @VisibleForTesting
- AdditionalViewContainer mHandleMenuViewContainer;
- // Position of the handle menu used for laying out the handle view.
- @VisibleForTesting
- final PointF mHandleMenuPosition = new PointF();
- // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
- // may be in a different coordinate space than the input coordinates. Therefore, we still care
- // about the menu's coordinates relative to the display as a whole, so we need to maintain
- // those as well.
- final Point mGlobalMenuPosition = new Point();
- private final boolean mShouldShowWindowingPill;
- private final boolean mShouldShowBrowserPill;
- private final Bitmap mAppIconBitmap;
- private final CharSequence mAppName;
- private final View.OnClickListener mOnClickListener;
- private final View.OnTouchListener mOnTouchListener;
- private final RunningTaskInfo mTaskInfo;
- private final DisplayController mDisplayController;
- private final SplitScreenController mSplitScreenController;
- private final int mLayoutResId;
- private int mMarginMenuTop;
- private int mMarginMenuStart;
- private int mMenuHeight;
- private int mMenuWidth;
- private final int mCaptionHeight;
- private HandleMenuAnimator mHandleMenuAnimator;
-
-
- HandleMenu(DesktopModeWindowDecoration parentDecor, int layoutResId,
- View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Bitmap appIcon, CharSequence appName, DisplayController displayController,
- SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
- boolean shouldShowBrowserPill, int captionHeight) {
- mParentDecor = parentDecor;
- mContext = mParentDecor.mDecorWindowContext;
- mTaskInfo = mParentDecor.mTaskInfo;
- mDisplayController = displayController;
- mSplitScreenController = splitScreenController;
- mLayoutResId = layoutResId;
- mOnClickListener = onClickListener;
- mOnTouchListener = onTouchListener;
- mAppIconBitmap = appIcon;
- mAppName = appName;
- mShouldShowWindowingPill = shouldShowWindowingPill;
- mShouldShowBrowserPill = shouldShowBrowserPill;
- mCaptionHeight = captionHeight;
- loadHandleMenuDimensions();
- updateHandleMenuPillPositions();
- }
-
- void show() {
- final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-
- createHandleMenuViewContainer(t, ssg);
- ssg.addTransaction(t);
- ssg.markSyncReady();
- setupHandleMenu();
- animateHandleMenu();
- }
-
- private void createHandleMenuViewContainer(SurfaceControl.Transaction t,
- SurfaceSyncGroup ssg) {
- final int x = (int) mHandleMenuPosition.x;
- final int y = (int) mHandleMenuPosition.y;
- if (!mTaskInfo.isFreeform() && Flags.enableAdditionalWindowsAboveStatusBar()) {
- mHandleMenuViewContainer = new AdditionalSystemViewContainer(mContext,
- R.layout.desktop_mode_window_decor_handle_menu, mTaskInfo.taskId,
- x, y, mMenuWidth, mMenuHeight);
- } else {
- mHandleMenuViewContainer = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, mMenuWidth, mMenuHeight);
- }
- final View handleMenuView = mHandleMenuViewContainer.getView();
- mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
- }
-
- /**
- * Animates the appearance of the handle menu and its three pills.
- */
- private void animateHandleMenu() {
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
- } else {
- mHandleMenuAnimator.animateOpen();
- }
- }
-
- /**
- * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
- * pill.
- */
- private void setupHandleMenu() {
- final View handleMenu = mHandleMenuViewContainer.getView();
- handleMenu.setOnTouchListener(mOnTouchListener);
- setupAppInfoPill(handleMenu);
- if (mShouldShowWindowingPill) {
- setupWindowingPill(handleMenu);
- }
- setupMoreActionsPill(handleMenu);
- setupOpenInBrowserPill(handleMenu);
- }
-
- /**
- * Set up interactive elements of handle menu's app info pill.
- */
- private void setupAppInfoPill(View handleMenu) {
- final HandleMenuImageButton collapseBtn =
- handleMenu.findViewById(R.id.collapse_menu_button);
- final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
- final TextView appName = handleMenu.findViewById(R.id.application_name);
- collapseBtn.setOnClickListener(mOnClickListener);
- collapseBtn.setTaskInfo(mTaskInfo);
- appIcon.setImageBitmap(mAppIconBitmap);
- appName.setText(mAppName);
- }
-
- /**
- * Set up interactive elements and color of handle menu's windowing pill.
- */
- private void setupWindowingPill(View handleMenu) {
- final ImageButton fullscreenBtn = handleMenu.findViewById(
- R.id.fullscreen_button);
- final ImageButton splitscreenBtn = handleMenu.findViewById(
- R.id.split_screen_button);
- final ImageButton floatingBtn = handleMenu.findViewById(R.id.floating_button);
- // TODO: Remove once implemented.
- floatingBtn.setVisibility(View.GONE);
-
- final ImageButton desktopBtn = handleMenu.findViewById(R.id.desktop_button);
- fullscreenBtn.setOnClickListener(mOnClickListener);
- splitscreenBtn.setOnClickListener(mOnClickListener);
- floatingBtn.setOnClickListener(mOnClickListener);
- desktopBtn.setOnClickListener(mOnClickListener);
- // The button corresponding to the windowing mode that the task is currently in uses a
- // different color than the others.
- final ColorStateList[] iconColors = getWindowingIconColor();
- final ColorStateList inActiveColorStateList = iconColors[0];
- final ColorStateList activeColorStateList = iconColors[1];
- final int windowingMode = mTaskInfo.getWindowingMode();
- fullscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_FULLSCREEN
- ? activeColorStateList : inActiveColorStateList);
- splitscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_MULTI_WINDOW
- ? activeColorStateList : inActiveColorStateList);
- floatingBtn.setImageTintList(windowingMode == WINDOWING_MODE_PINNED
- ? activeColorStateList : inActiveColorStateList);
- desktopBtn.setImageTintList(windowingMode == WINDOWING_MODE_FREEFORM
- ? activeColorStateList : inActiveColorStateList);
- }
-
- /**
- * Set up interactive elements & height of handle menu's more actions pill
- */
- private void setupMoreActionsPill(View handleMenu) {
- if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
- handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
- }
- }
-
- private void setupOpenInBrowserPill(View handleMenu) {
- if (!mShouldShowBrowserPill) {
- handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE);
- return;
- }
- final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button);
- browserButton.setOnClickListener(mOnClickListener);
- }
-
- /**
- * Returns array of windowing icon color based on current UI theme. First element of the
- * array is for inactive icons and the second is for active icons.
- */
- private ColorStateList[] getWindowingIconColor() {
- final int mode = mContext.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK;
- final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
- final TypedArray typedArray = mContext.obtainStyledAttributes(new int[]{
- com.android.internal.R.attr.materialColorOnSurface,
- com.android.internal.R.attr.materialColorPrimary});
- final int inActiveColor = typedArray.getColor(0, isNightMode ? Color.WHITE : Color.BLACK);
- final int activeColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
- typedArray.recycle();
- return new ColorStateList[]{ColorStateList.valueOf(inActiveColor),
- ColorStateList.valueOf(activeColor)};
- }
-
- /**
- * Updates handle menu's position variables to reflect its next position.
- */
- private void updateHandleMenuPillPositions() {
- int menuX;
- final int menuY;
- final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
- updateGlobalMenuPosition(taskBounds);
- if (mLayoutResId == R.layout.desktop_mode_app_header) {
- // Align the handle menu to the left side of the caption.
- menuX = mMarginMenuStart;
- menuY = mMarginMenuTop;
- } else {
- if (Flags.enableAdditionalWindowsAboveStatusBar()) {
- // In a focused decor, we use global coordinates for handle menu. Therefore we
- // need to account for other factors like split stage and menu/handle width to
- // center the menu.
- final DisplayLayout layout = mDisplayController
- .getDisplayLayout(mTaskInfo.displayId);
- menuX = mGlobalMenuPosition.x + ((mMenuWidth - layout.width()) / 2);
- menuY = mGlobalMenuPosition.y + ((mMenuHeight - layout.height()) / 2);
- } else {
- menuX = (taskBounds.width() / 2) - (mMenuWidth / 2);
- menuY = mMarginMenuTop;
- }
- }
- // Handle Menu position setup.
- mHandleMenuPosition.set(menuX, menuY);
- }
-
- private void updateGlobalMenuPosition(Rect taskBounds) {
- if (mTaskInfo.isFreeform()) {
- mGlobalMenuPosition.set(taskBounds.left + mMarginMenuStart,
- taskBounds.top + mMarginMenuTop);
- } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- mGlobalMenuPosition.set(
- (taskBounds.width() / 2) - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop
- );
- } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- final int splitPosition = mSplitScreenController.getSplitPosition(mTaskInfo.taskId);
- final Rect leftOrTopStageBounds = new Rect();
- final Rect rightOrBottomStageBounds = new Rect();
- mSplitScreenController.getStageBounds(leftOrTopStageBounds,
- rightOrBottomStageBounds);
- // TODO(b/343561161): This needs to be calculated differently if the task is in
- // top/bottom split.
- if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- mGlobalMenuPosition.set(leftOrTopStageBounds.width()
- + (rightOrBottomStageBounds.width() / 2)
- - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop);
- } else if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
- mGlobalMenuPosition.set((leftOrTopStageBounds.width() / 2)
- - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop);
- }
- }
- }
-
- /**
- * Update pill layout, in case task changes have caused positioning to change.
- */
- void relayout(SurfaceControl.Transaction t) {
- if (mHandleMenuViewContainer != null) {
- updateHandleMenuPillPositions();
- mHandleMenuViewContainer.setPosition(t, mHandleMenuPosition.x, mHandleMenuPosition.y);
- }
- }
-
- /**
- * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
- * Note this should only be called when a regular onClick/onHover is not possible
- * (i.e. the button was clicked through status bar layer)
- *
- * @param ev the MotionEvent to compare against.
- */
- void checkMotionEvent(MotionEvent ev) {
- // If the menu view is above status bar, we can let the views handle input directly.
- if (isViewAboveStatusBar()) return;
- final View handleMenu = mHandleMenuViewContainer.getView();
- final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
- final PointF inputPoint = translateInputToLocalSpace(ev);
- final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y);
- final int action = ev.getActionMasked();
- collapse.setHovered(inputInCollapseButton && action != ACTION_UP);
- collapse.setPressed(inputInCollapseButton && action == ACTION_DOWN);
- if (action == ACTION_UP && inputInCollapseButton) {
- collapse.performClick();
- }
- }
-
- private boolean isViewAboveStatusBar() {
- return Flags.enableAdditionalWindowsAboveStatusBar()
- && !mTaskInfo.isFreeform();
- }
-
- // Translate the input point from display coordinates to the same space as the handle menu.
- private PointF translateInputToLocalSpace(MotionEvent ev) {
- return new PointF(ev.getX() - mHandleMenuPosition.x,
- ev.getY() - mHandleMenuPosition.y);
- }
-
- /**
- * A valid menu input is one of the following:
- * An input that happens in the menu views.
- * Any input before the views have been laid out.
- *
- * @param inputPoint the input to compare against.
- */
- boolean isValidMenuInput(PointF inputPoint) {
- if (!viewsLaidOut()) return true;
- if (!isViewAboveStatusBar()) {
- return pointInView(
- mHandleMenuViewContainer.getView(),
- inputPoint.x - mHandleMenuPosition.x,
- inputPoint.y - mHandleMenuPosition.y);
- } else {
- // Handle menu exists in a different coordinate space when added to WindowManager.
- // Therefore we must compare the provided input coordinates to global menu coordinates.
- // This includes factoring for split stage as input coordinates are relative to split
- // stage position, not relative to the display as a whole.
- PointF inputRelativeToMenu = new PointF(
- inputPoint.x - mGlobalMenuPosition.x,
- inputPoint.y - mGlobalMenuPosition.y
- );
- if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
- == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- // TODO(b/343561161): This also needs to be calculated differently if
- // the task is in top/bottom split.
- Rect leftStageBounds = new Rect();
- mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
- inputRelativeToMenu.x += leftStageBounds.width();
- }
- return pointInView(
- mHandleMenuViewContainer.getView(),
- inputRelativeToMenu.x,
- inputRelativeToMenu.y);
- }
- }
-
- private boolean pointInView(View v, float x, float y) {
- return v != null && v.getLeft() <= x && v.getRight() >= x
- && v.getTop() <= y && v.getBottom() >= y;
- }
-
- /**
- * Check if the views for handle menu can be seen.
- */
- private boolean viewsLaidOut() {
- return mHandleMenuViewContainer.getView().isLaidOut();
- }
-
- private void loadHandleMenuDimensions() {
- final Resources resources = mContext.getResources();
- mMenuWidth = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_width);
- mMenuHeight = getHandleMenuHeight(resources);
- mMarginMenuTop = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_top);
- mMarginMenuStart = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_start);
- }
-
- /**
- * Determines handle menu height based on if windowing pill should be shown.
- */
- private int getHandleMenuHeight(Resources resources) {
- int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
- if (!mShouldShowWindowingPill) {
- menuHeight -= loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height);
- }
- if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
- menuHeight -= loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
- }
- if (!mShouldShowBrowserPill) {
- menuHeight -= loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height);
- }
- return menuHeight;
- }
-
- private int loadDimensionPixelSize(Resources resources, int resourceId) {
- if (resourceId == Resources.ID_NULL) {
- return 0;
- }
- return resources.getDimensionPixelSize(resourceId);
- }
-
- void close() {
- final Runnable after = () -> {
- mHandleMenuViewContainer.releaseView();
- mHandleMenuViewContainer = null;
- };
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
- } else {
- mHandleMenuAnimator.animateClose(after);
- }
- }
-
- static final class Builder {
- private final DesktopModeWindowDecoration mParent;
- private CharSequence mName;
- private Bitmap mAppIcon;
- private View.OnClickListener mOnClickListener;
- private View.OnTouchListener mOnTouchListener;
- private int mLayoutId;
- private boolean mShowWindowingPill;
- private int mCaptionHeight;
- private DisplayController mDisplayController;
- private SplitScreenController mSplitScreenController;
- private boolean mShowBrowserPill;
-
- Builder(@NonNull DesktopModeWindowDecoration parent) {
- mParent = parent;
- }
-
- Builder setAppName(@Nullable CharSequence name) {
- mName = name;
- return this;
- }
-
- Builder setAppIcon(@Nullable Bitmap appIcon) {
- mAppIcon = appIcon;
- return this;
- }
-
- Builder setOnClickListener(@Nullable View.OnClickListener onClickListener) {
- mOnClickListener = onClickListener;
- return this;
- }
-
- Builder setOnTouchListener(@Nullable View.OnTouchListener onTouchListener) {
- mOnTouchListener = onTouchListener;
- return this;
- }
-
- Builder setLayoutId(int layoutId) {
- mLayoutId = layoutId;
- return this;
- }
-
- Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
- mShowWindowingPill = windowingButtonsVisible;
- return this;
- }
-
- Builder setCaptionHeight(int captionHeight) {
- mCaptionHeight = captionHeight;
- return this;
- }
-
- Builder setDisplayController(DisplayController displayController) {
- mDisplayController = displayController;
- return this;
- }
-
- Builder setSplitScreenController(SplitScreenController splitScreenController) {
- mSplitScreenController = splitScreenController;
- return this;
- }
-
- Builder setBrowserLinkAvailable(Boolean showBrowserPill) {
- mShowBrowserPill = showBrowserPill;
- return this;
- }
-
- HandleMenu build() {
- return new HandleMenu(mParent, mLayoutId, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
- mShowWindowingPill, mShowBrowserPill, mCaptionHeight);
- }
- }
-}
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
new file mode 100644
index 0000000..bce233f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.annotation.DimenRes
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+import android.view.View
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.SurfaceSyncGroup
+import androidx.annotation.VisibleForTesting
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.split.SplitScreenConstants
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.extension.isFullscreen
+
+/**
+ * Handle menu opened when the appropriate button is clicked on.
+ *
+ * Displays up to 3 pills that show the following:
+ * App Info: App name, app icon, and collapse button to close the menu.
+ * Windowing Options(Proto 2 only): Buttons to change windowing modes.
+ * Additional Options: Miscellaneous functions including screenshot and closing task.
+ */
+class HandleMenu(
+ private val parentDecor: DesktopModeWindowDecoration,
+ private val layoutResId: Int,
+ private val onClickListener: View.OnClickListener?,
+ private val onTouchListener: View.OnTouchListener?,
+ private val appIconBitmap: Bitmap?,
+ private val appName: CharSequence?,
+ private val displayController: DisplayController,
+ private val splitScreenController: SplitScreenController,
+ private val shouldShowWindowingPill: Boolean,
+ private val shouldShowBrowserPill: Boolean,
+ private val captionHeight: Int
+) {
+ private val context: Context = parentDecor.mDecorWindowContext
+ private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
+
+ private val isViewAboveStatusBar: Boolean
+ get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
+
+ private val pillElevation: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_elevation)
+ private val pillTopMargin: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ private val menuWidth = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_width) + pillElevation
+ private val menuHeight = getHandleMenuHeight()
+ private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
+ private val marginMenuStart = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_margin_start)
+
+ private var handleMenuAnimator: HandleMenuAnimator? = null
+
+ @VisibleForTesting
+ var handleMenuViewContainer: AdditionalViewContainer? = null
+
+ // Position of the handle menu used for laying out the handle view.
+ @VisibleForTesting
+ val handleMenuPosition: PointF = PointF()
+
+ // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
+ // may be in a different coordinate space than the input coordinates. Therefore, we still care
+ // about the menu's coordinates relative to the display as a whole, so we need to maintain
+ // those as well.
+ private val globalMenuPosition: Point = Point()
+
+ /**
+ * An a array of windowing icon color based on current UI theme. First element of the
+ * array is for inactive icons and the second is for active icons.
+ */
+ private val windowingIconColor: Array<ColorStateList>
+ get() {
+ val mode = (context.resources.configuration.uiMode
+ and Configuration.UI_MODE_NIGHT_MASK)
+ val isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES)
+ val typedArray = context.obtainStyledAttributes(
+ intArrayOf(
+ com.android.internal.R.attr.materialColorOnSurface,
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ )
+ val inActiveColor =
+ typedArray.getColor(0, if (isNightMode) Color.WHITE else Color.BLACK)
+ val activeColor = typedArray.getColor(1, if (isNightMode) Color.WHITE else Color.BLACK)
+ typedArray.recycle()
+ return arrayOf(
+ ColorStateList.valueOf(inActiveColor),
+ ColorStateList.valueOf(activeColor)
+ )
+ }
+
+ init {
+ updateHandleMenuPillPositions()
+ }
+
+ fun show() {
+ val ssg = SurfaceSyncGroup(TAG)
+ val t = SurfaceControl.Transaction()
+
+ createHandleMenuViewContainer(t, ssg)
+ ssg.addTransaction(t)
+ ssg.markSyncReady()
+ setupHandleMenu()
+ animateHandleMenu()
+ }
+
+ private fun createHandleMenuViewContainer(
+ t: SurfaceControl.Transaction,
+ ssg: SurfaceSyncGroup
+ ) {
+ val x = handleMenuPosition.x.toInt()
+ val y = handleMenuPosition.y.toInt()
+ handleMenuViewContainer =
+ if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
+ AdditionalSystemViewContainer(
+ context = context,
+ layoutId = R.layout.desktop_mode_window_decor_handle_menu,
+ taskId = taskInfo.taskId,
+ x = x,
+ y = y,
+ width = menuWidth,
+ height = menuHeight
+ )
+ } else {
+ parentDecor.addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+ t, ssg, x, y, menuWidth, menuHeight
+ )
+ }
+ handleMenuViewContainer?.view?.let { view ->
+ handleMenuAnimator =
+ HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
+ }
+ }
+
+ /**
+ * Animates the appearance of the handle menu and its three pills.
+ */
+ private fun animateHandleMenu() {
+ when (taskInfo.windowingMode) {
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ handleMenuAnimator?.animateCaptionHandleExpandToOpen()
+ }
+ else -> {
+ handleMenuAnimator?.animateOpen()
+ }
+ }
+ }
+
+ /**
+ * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
+ * pill.
+ */
+ private fun setupHandleMenu() {
+ val handleMenu = handleMenuViewContainer?.view ?: return
+ handleMenu.setOnTouchListener(onTouchListener)
+ setupAppInfoPill(handleMenu)
+ if (shouldShowWindowingPill) {
+ setupWindowingPill(handleMenu)
+ }
+ setupMoreActionsPill(handleMenu)
+ setupOpenInBrowserPill(handleMenu)
+ }
+
+ /**
+ * Set up interactive elements of handle menu's app info pill.
+ */
+ private fun setupAppInfoPill(handleMenu: View) {
+ val collapseBtn = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+ val appIcon = handleMenu.findViewById<ImageView>(R.id.application_icon)
+ val appName = handleMenu.findViewById<TextView>(R.id.application_name)
+ collapseBtn.setOnClickListener(onClickListener)
+ collapseBtn.taskInfo = taskInfo
+ appIcon.setImageBitmap(appIconBitmap)
+ appName.text = this.appName
+ }
+
+ /**
+ * Set up interactive elements and color of handle menu's windowing pill.
+ */
+ private fun setupWindowingPill(handleMenu: View) {
+ val fullscreenBtn = handleMenu.findViewById<ImageButton>(R.id.fullscreen_button)
+ val splitscreenBtn = handleMenu.findViewById<ImageButton>(R.id.split_screen_button)
+ val floatingBtn = handleMenu.findViewById<ImageButton>(R.id.floating_button)
+ // TODO: Remove once implemented.
+ floatingBtn.visibility = View.GONE
+
+ val desktopBtn = handleMenu.findViewById<ImageButton>(R.id.desktop_button)
+ fullscreenBtn.setOnClickListener(onClickListener)
+ splitscreenBtn.setOnClickListener(onClickListener)
+ floatingBtn.setOnClickListener(onClickListener)
+ desktopBtn.setOnClickListener(onClickListener)
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ val iconColors = windowingIconColor
+ val inActiveColorStateList = iconColors[0]
+ val activeColorStateList = iconColors[1]
+ fullscreenBtn.imageTintList = if (taskInfo.isFullscreen) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ splitscreenBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ floatingBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_PINNED) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ desktopBtn.imageTintList = if (taskInfo.isFreeform) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ }
+
+ /**
+ * Set up interactive elements & height of handle menu's more actions pill
+ */
+ private fun setupMoreActionsPill(handleMenu: View) {
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ handleMenu.findViewById<View>(R.id.more_actions_pill).visibility = View.GONE
+ }
+ }
+
+ private fun setupOpenInBrowserPill(handleMenu: View) {
+ if (!shouldShowBrowserPill) {
+ handleMenu.findViewById<View>(R.id.open_in_browser_pill).visibility = View.GONE
+ return
+ }
+ val browserButton = handleMenu.findViewById<Button>(R.id.open_in_browser_button)
+ browserButton.setOnClickListener(onClickListener)
+ }
+
+ /**
+ * Updates handle menu's position variables to reflect its next position.
+ */
+ private fun updateHandleMenuPillPositions() {
+ val menuX: Int
+ val menuY: Int
+ val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
+ updateGlobalMenuPosition(taskBounds)
+ if (layoutResId == R.layout.desktop_mode_app_header) {
+ // Align the handle menu to the left side of the caption.
+ menuX = marginMenuStart
+ menuY = marginMenuTop
+ } else {
+ if (Flags.enableAdditionalWindowsAboveStatusBar()) {
+ // In a focused decor, we use global coordinates for handle menu. Therefore we
+ // need to account for other factors like split stage and menu/handle width to
+ // center the menu.
+ menuX = globalMenuPosition.x
+ menuY = globalMenuPosition.y
+ } else {
+ menuX = (taskBounds.width() / 2) - (menuWidth / 2)
+ menuY = marginMenuTop
+ }
+ }
+ // Handle Menu position setup.
+ handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
+ }
+
+ private fun updateGlobalMenuPosition(taskBounds: Rect) {
+ when (taskInfo.windowingMode) {
+ WINDOWING_MODE_FREEFORM -> {
+ globalMenuPosition.set(
+ /* x = */ taskBounds.left + marginMenuStart,
+ /* y = */ taskBounds.top + marginMenuTop
+ )
+ }
+ WINDOWING_MODE_FULLSCREEN -> {
+ globalMenuPosition.set(
+ /* x = */ taskBounds.width() / 2 - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
+ val leftOrTopStageBounds = Rect()
+ val rightOrBottomStageBounds = Rect()
+ splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
+ // TODO(b/343561161): This needs to be calculated differently if the task is in
+ // top/bottom split.
+ when (splitPosition) {
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
+ globalMenuPosition.set(
+ /* x = */ leftOrTopStageBounds.width()
+ + (rightOrBottomStageBounds.width() / 2)
+ - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
+ globalMenuPosition.set(
+ /* x = */ (leftOrTopStageBounds.width() / 2)
+ - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update pill layout, in case task changes have caused positioning to change.
+ */
+ fun relayout(t: SurfaceControl.Transaction) {
+ handleMenuViewContainer?.let { container ->
+ updateHandleMenuPillPositions()
+ container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y)
+ }
+ }
+
+ /**
+ * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
+ * Note this should only be called when a regular onClick/onHover is not possible
+ * (i.e. the button was clicked through status bar layer)
+ *
+ * @param ev the MotionEvent to compare against.
+ */
+ fun checkMotionEvent(ev: MotionEvent) {
+ // If the menu view is above status bar, we can let the views handle input directly.
+ if (isViewAboveStatusBar) return
+ val handleMenu = handleMenuViewContainer?.view ?: return
+ val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+ val inputPoint = translateInputToLocalSpace(ev)
+ val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
+ val action = ev.actionMasked
+ collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
+ collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
+ if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+ collapse.performClick()
+ }
+ }
+
+ // Translate the input point from display coordinates to the same space as the handle menu.
+ private fun translateInputToLocalSpace(ev: MotionEvent): PointF {
+ return PointF(
+ ev.x - handleMenuPosition.x,
+ ev.y - handleMenuPosition.y
+ )
+ }
+
+ /**
+ * A valid menu input is one of the following:
+ * An input that happens in the menu views.
+ * Any input before the views have been laid out.
+ *
+ * @param inputPoint the input to compare against.
+ */
+ fun isValidMenuInput(inputPoint: PointF): Boolean {
+ if (!viewsLaidOut()) return true
+ if (!isViewAboveStatusBar) {
+ return pointInView(
+ handleMenuViewContainer?.view,
+ inputPoint.x - handleMenuPosition.x,
+ inputPoint.y - handleMenuPosition.y
+ )
+ } else {
+ // Handle menu exists in a different coordinate space when added to WindowManager.
+ // Therefore we must compare the provided input coordinates to global menu coordinates.
+ // This includes factoring for split stage as input coordinates are relative to split
+ // stage position, not relative to the display as a whole.
+ val inputRelativeToMenu = PointF(
+ inputPoint.x - globalMenuPosition.x,
+ inputPoint.y - globalMenuPosition.y
+ )
+ if (splitScreenController.getSplitPosition(taskInfo.taskId)
+ == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ // TODO(b/343561161): This also needs to be calculated differently if
+ // the task is in top/bottom split.
+ val leftStageBounds = Rect()
+ splitScreenController.getStageBounds(leftStageBounds, Rect())
+ inputRelativeToMenu.x += leftStageBounds.width().toFloat()
+ }
+ return pointInView(
+ handleMenuViewContainer?.view,
+ inputRelativeToMenu.x,
+ inputRelativeToMenu.y
+ )
+ }
+ }
+
+ private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+ return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ /**
+ * Check if the views for handle menu can be seen.
+ */
+ private fun viewsLaidOut(): Boolean = handleMenuViewContainer?.view?.isLaidOut ?: false
+
+ /**
+ * Determines handle menu height based the max size and the visibility of pills.
+ */
+ private fun getHandleMenuHeight(): Int {
+ var menuHeight = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_height) + pillElevation
+ if (!shouldShowWindowingPill) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ if (!shouldShowBrowserPill) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ return menuHeight
+ }
+
+ private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) {
+ return 0
+ }
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
+
+ fun close() {
+ val after = {
+ handleMenuViewContainer?.releaseView()
+ handleMenuViewContainer = null
+ }
+ if (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ handleMenuAnimator?.animateCollapseIntoHandleClose(after)
+ } else {
+ handleMenuAnimator?.animateClose(after)
+ }
+ }
+
+ companion object {
+ private const val TAG = "HandleMenu"
+ private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 25a829b..e3d2234 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -108,7 +108,7 @@
*
* @param after runs after the animation finishes.
*/
- fun animateCollapseIntoHandleClose(after: Runnable) {
+ fun animateCollapseIntoHandleClose(after: () -> Unit) {
appInfoCollapseToHandle()
animateAppInfoPillFadeOut()
windowingPillClose()
@@ -125,7 +125,7 @@
* @param after runs after animation finishes.
*
*/
- fun animateClose(after: Runnable) {
+ fun animateClose(after: () -> Unit) {
appInfoPillCollapse()
animateAppInfoPillFadeOut()
windowingPillClose()
@@ -463,9 +463,9 @@
*
* @param after runs after animation finishes.
*/
- private fun runAnimations(after: Runnable? = null) {
+ private fun runAnimations(after: (() -> Unit)? = null) {
runningAnimation?.apply {
- // Remove all listeners, so that after runnable isn't triggered upon cancel.
+ // Remove all listeners, so that the after function isn't triggered upon cancel.
removeAllListeners()
// If an animation runs while running animation is triggered, gracefully cancel.
cancel()
@@ -475,7 +475,7 @@
playTogether(animators)
animators.clear()
doOnEnd {
- after?.run()
+ after?.invoke()
runningAnimation = null
}
start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 1532211..b5b476d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW;
import android.graphics.Point;
@@ -103,6 +104,9 @@
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
mTaskOrganizer.applyTransaction(wct);
}
+ } else {
+ mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
+ mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
@@ -157,11 +161,16 @@
}
mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
} else {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
- wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ if (!mTaskBoundsAtDragStart.equals(mRepositionTaskBounds)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ } else {
+ // Drag-move ended where it originally started, no need to update WM.
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ }
}
mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -202,6 +211,7 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
finishCallback.onTransitionFinished(null);
mIsResizingOrAnimatingResize = false;
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 03dbbb3..d212f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -53,6 +54,7 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -105,7 +107,7 @@
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
- final DisplayController mDisplayController;
+ final @NonNull DisplayController mDisplayController;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -158,7 +160,7 @@
WindowDecoration(
Context context,
- DisplayController displayController,
+ @NonNull DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
@NonNull SurfaceControl taskSurface,
@@ -759,9 +761,12 @@
}
void addOrUpdate(WindowContainerTransaction wct) {
- wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects);
+ final @InsetsSource.Flags int captionSourceFlags =
+ Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
+ wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
+ captionSourceFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
- mBoundingRects);
+ mBoundingRects, 0 /* flags */);
}
void remove(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 6c2c8fd..4897f76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.PixelFormat
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.View
@@ -45,9 +46,11 @@
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT
- )
- lp.title = "Additional view container of Task=$taskId"
- lp.setTrustedOverlay()
+ ).apply {
+ title = "Additional view container of Task=$taskId"
+ gravity = Gravity.LEFT or Gravity.TOP
+ setTrustedOverlay()
+ }
val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
wm?.addView(view, lp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
new file mode 100644
index 0000000..bbf0ce5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowMultiWindow.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base scenario test for window drag CUJ with multiple windows. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase()
+{
+ private val imeAppHelper = ImeAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ override fun dragAppWindow() {
+ val (startXIme, startYIme) = getWindowDragStartCoordinate(imeAppHelper)
+
+ imeApp.dragWindow(startXIme, startYIme,
+ endX = startXIme + 150, endY = startYIme + 150,
+ wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
new file mode 100644
index 0000000..a613ca1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowScenarioTestBase.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base test class for window drag CUJ. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowScenarioTestBase {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Test abstract fun dragAppWindow()
+
+ /** Return the top-center coordinate of the app header as the start coordinate. */
+ fun getWindowDragStartCoordinate(appHelper: StandardAppHelper): Pair<Int, Int> {
+ val windowRect = wmHelper.getWindowRegion(appHelper).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+ return Pair(startX, startY)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
new file mode 100644
index 0000000..0655620
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/DragAppWindowSingleWindow.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base scenario test for window drag CUJ with single window. */
+@Ignore("Base Test Class")
+abstract class DragAppWindowSingleWindow : DragAppWindowScenarioTestBase()
+{
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ override fun dragAppWindow() {
+ val (startXTest, startYTest) = getWindowDragStartCoordinate(simpleAppHelper)
+ testApp.dragWindow(startXTest, startYTest,
+ endX = startXTest + 150, endY = startYTest + 150,
+ wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
new file mode 100644
index 0000000..0b6c9af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+
+@Ignore("Base Test Class")
+abstract class ExitDesktopWithDragToTopDragZone
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun exitDesktopWithDragToTopDragZone() {
+ testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
new file mode 100644
index 0000000..1a86cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link DefaultCompatUIRepository}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultCompatUIRepositoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultCompatUIRepositoryTest {
+
+ lateinit var repository: CompatUIRepository
+
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setUp() {
+ repository = DefaultCompatUIRepository()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `addSpec throws exception with specs with duplicate id`() {
+ repository.addSpec(CompatUISpec("one"))
+ repository.addSpec(CompatUISpec("one"))
+ }
+
+ @Test
+ fun `iterateOn invokes the consumer`() {
+ with(repository) {
+ addSpec(CompatUISpec("one"))
+ addSpec(CompatUISpec("two"))
+ addSpec(CompatUISpec("three"))
+ val consumer = object : (CompatUISpec) -> Unit {
+ var acc = ""
+ override fun invoke(spec: CompatUISpec) {
+ acc += spec.name
+ }
+ }
+ iterateOn(consumer)
+ assertEquals("onetwothree", consumer.acc)
+ }
+ }
+
+ @Test
+ fun `findSpec returns existing specs`() {
+ with(repository) {
+ val one = CompatUISpec("one")
+ val two = CompatUISpec("two")
+ val three = CompatUISpec("three")
+ addSpec(one)
+ addSpec(two)
+ addSpec(three)
+ assertEquals(findSpec("one"), one)
+ assertEquals(findSpec("two"), two)
+ assertEquals(findSpec("three"), three)
+ assertNull(findSpec("abc"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
new file mode 100644
index 0000000..cdc524a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Fake implementation for {@link CompatUIRepository}
+ */
+class FakeCompatUIRepository : CompatUIRepository {
+ val allSpecs = mutableMapOf<String, CompatUISpec>()
+ override fun addSpec(spec: CompatUISpec) {
+ if (findSpec(spec.name) != null) {
+ throw IllegalStateException("Spec with name:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 6612aee..18b08bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -337,65 +337,65 @@
}
@Test
- fun getVisibleTaskCount() {
+ fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
// No tasks, count is 0
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
- assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_multipleDisplays() {
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -494,28 +494,6 @@
}
@Test
- fun isDesktopModeShowing_noActiveTasks_returnsFalse() {
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
- repo.updateVisibleFreeformTasks(displayId = 0, taskId = 1, visible = true)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isTrue()
- }
-
- @Test
fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
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 6cabbf9..da88686 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
@@ -20,7 +20,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -68,6 +67,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -125,11 +125,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -311,6 +311,50 @@
}
@Test
+ fun isDesktopModeShowing_noTasks_returnsFalse() {
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_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: home, task1, task2 (no wallpaper intent)
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
@@ -330,6 +374,25 @@
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_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: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
@@ -427,6 +490,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
@@ -436,10 +500,13 @@
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: wallpaper intent, task
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Move freeform task to front
+ wct.assertReorderAt(index = 2, taskDefaultDisplay)
}
@Test
@@ -464,7 +531,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -474,40 +541,42 @@
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
// Reorder freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ wct.assertReorderAt(index = 2, freeformTask, toTop = true)
}
@Test
- fun getVisibleTaskCount_noTasks_returnsZero() {
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_noTasks_returnsZero() {
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
}
@Test
- fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskHidden)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
}
@Test
- fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
setUpHomeTask()
setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -861,16 +930,19 @@
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+ assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+ // Move home to front
+ wct.assertReorderAt(0, homeTask)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ // Bring freeform tasks to front
wct.assertReorderSequenceInRange(
- range = 1..<(taskLimit + 1),
+ range = 2..<(taskLimit + 2),
*freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
newTask
)
@@ -888,6 +960,24 @@
}
@Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -899,6 +989,44 @@
}
@Test
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -1348,7 +1476,7 @@
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build()
- val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
val result = controller.handleRequest(Binder(), transition)
assertThat(result).isNull()
}
@@ -1442,8 +1570,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1452,8 +1583,22 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1462,8 +1607,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1473,22 +1621,42 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1499,8 +1667,24 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1511,8 +1695,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1521,14 +1708,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1537,24 +1743,53 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -1563,8 +1798,35 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1574,22 +1836,71 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 0, task1.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1600,20 +1911,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1622,14 +1924,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1638,9 +1959,45 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
}
@Test
@@ -1722,6 +2079,49 @@
}
@Test
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
@@ -1930,6 +2330,7 @@
eq(null))
}
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -1939,14 +2340,67 @@
task2.isFocused = true
task3.isFocused = false
- controller.enterSplit(DEFAULT_DISPLAY, false)
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
verify(splitScreenController)
.requestEnterSplitSelect(
- task2,
+ eq(task2),
any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ }
+
+ @Test
+ fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Removes wallpaper activity when leaving desktop
+ wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.value.hierarchyOps).isEmpty()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6888de5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..ace09a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 6d18e36..92762fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
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 754a173..6bc7e49 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
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.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -185,6 +186,73 @@
verify(startT, never()).setColor(any(), any());
}
+ @Test
+ public void startAnimation_freeformOpenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeformMinimizeChange_underFullscreenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeform_minimizeAnimation_reparentsTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT).reparent(any(), any());
+ }
+
private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
handler.mergeAnimation(
new Binder(),
@@ -195,10 +263,14 @@
}
private static RunningTaskInfo createTaskInfo(int taskId) {
+ return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
taskInfo.token = mock(WindowContainerToken.class);
return taskInfo;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
new file mode 100644
index 0000000..bad14bb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_TRANSLUCENT
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+class TransitionAnimationHelperTest : ShellTestCase() {
+
+ @Mock
+ lateinit var transitionAnimation: TransitionAnimation
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_returnsMinimizeAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskToFront_taskToFrontChange_returnsUnminimizeAnim() {
+ val toFrontChange = ChangeBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .addChange(toFrontChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_TO_FRONT, info, toFrontChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_fullscreen_taskOpen_returnsTaskOpenEnterAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN).addChange(openChange).build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, openChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_taskOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_passesTranslucent() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation),
+ /* translucent= */ eq(true))
+ }
+
+ private fun loadAttributeAnimation(
+ @WindowManager.TransitionType type: Int,
+ info: TransitionInfo,
+ change: TransitionInfo.Change,
+ wallpaperTransit: Int = TransitionAnimation.WALLPAPER_TRANSITION_NONE,
+ isDreamTransition: Boolean = false,
+ ) {
+ TransitionAnimationHelper.loadAttributeAnimation(
+ type, info, change, wallpaperTransit, transitionAnimation, isDreamTransition)
+ }
+
+ private fun createTaskInfo(windowingMode: Int): RunningTaskInfo {
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setWindowingMode(windowingMode)
+ .build()
+ return taskInfo
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
new file mode 100644
index 0000000..261d4b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.WindowInsetsController
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CaptionWindowDecorationTests : ShellTestCase() {
+ @Test
+ fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
+ val taskInfo = createTaskInfo()
+ taskInfo.configuration.windowConfiguration.windowingMode =
+ WindowConfiguration.WINDOWING_MODE_FREEFORM
+ taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance =
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
+ val relayoutParams = WindowDecoration.RelayoutParams()
+
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+
+ Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
+ }
+
+ @Test
+ fun updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
+ val taskInfo = createTaskInfo()
+ taskInfo.configuration.windowConfiguration.windowingMode =
+ WindowConfiguration.WINDOWING_MODE_FREEFORM
+ taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = 0
+ val relayoutParams = WindowDecoration.RelayoutParams()
+
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+
+ Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
+ }
+
+ @Test
+ fun updateRelayoutParams_addOccludingCaptionElementCorrectly() {
+ val taskInfo = createTaskInfo()
+ val relayoutParams = WindowDecoration.RelayoutParams()
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
+ WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.START)
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements[1].mAlignment).isEqualTo(
+ WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.END)
+ }
+
+ private fun createTaskInfo(): ActivityManager.RunningTaskInfo {
+ val taskDescriptionBuilder =
+ ActivityManager.TaskDescription.Builder()
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build()
+ taskInfo.realActivity = ComponentName(
+ "com.android.wm.shell.windowdecor",
+ "CaptionWindowDecorationTests"
+ )
+ taskInfo.baseActivity = ComponentName(
+ "com.android.wm.shell.windowdecor",
+ "CaptionWindowDecorationTests"
+ )
+ return taskInfo
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 4dea5a7..6a94cd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -55,8 +55,6 @@
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
- private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
- + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -91,14 +89,13 @@
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
+ .addEqualityGroup(
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5))
.testEquals();
}
@@ -124,21 +121,21 @@
private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is still contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
// Vertically along the edge is contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
}
/**
@@ -151,10 +148,7 @@
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
GEOMETRY.union(region);
- // Make sure we're choosing a point outside of any debug region buffer.
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
@@ -168,9 +162,7 @@
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = FINE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 0c50ab6..adda9a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -22,10 +22,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.Color
+import android.graphics.Point
import android.graphics.Rect
-import android.platform.test.annotations.RequiresFlagsEnabled
-import android.platform.test.flag.junit.CheckFlagsRule
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -33,6 +33,7 @@
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
+import androidx.core.graphics.toPointF
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.R
@@ -46,6 +47,7 @@
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -69,7 +71,7 @@
class HandleMenuTest : ShellTestCase() {
@JvmField
@Rule
- val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
@Mock
private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
@@ -92,6 +94,8 @@
private lateinit var handleMenu: HandleMenu
+ private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
+
@Before
fun setUp() {
val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -100,7 +104,7 @@
) {
SurfaceControl.Transaction()
}
- val menuView = LayoutInflater.from(context).inflate(
+ val menuView = LayoutInflater.from(mContext).inflate(
R.layout.desktop_mode_window_decor_handle_menu, null)
whenever(mockDesktopWindowDecoration.addWindow(
anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
@@ -110,50 +114,69 @@
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.isLandscape).thenReturn(true)
- mockDesktopWindowDecoration.mDecorWindowContext = context
+ mContext.orCreateTestableResources.apply {
+ addOverride(R.dimen.desktop_mode_handle_menu_width, MENU_WIDTH)
+ addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
+ addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
+ addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
+ addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
+ addOverride(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
+ }
+ mockDesktopWindowDecoration.mDecorWindowContext = mContext
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFullscreenMenuUsesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
val handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
- assertTrue(handleMenu.mHandleMenuPosition.equals(16f, -512f))
+ val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFreeformMenu_usesViewHostViewContainer() {
createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalViewHostViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer)
// Verify menu is created near top-left of task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(12f, 8f))
+ val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN)
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitLeftMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
- // show at the top of split left task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(-624f, -512f))
+ // show at the top-center of split left task.
+ val expected = Point(
+ SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitRightMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
- // show at the top of split right task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(656f, -512f))
+ // show at the top-center of split right task.
+ val expected = Point(
+ SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
private fun createTaskInfo(windowingMode: Int, splitPosition: Int) {
@@ -178,14 +201,10 @@
.setBounds(bounds)
.setVisible(true)
.build()
- // Calculate captionX similar to how WindowDecoration calculates it.
- whenever(mockDesktopWindowDecoration.captionX).thenReturn(
- (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .bounds.width() - context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_decor_caption_width)) / 2)
whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition)
whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer {
(it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS)
+ (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS)
}
}
@@ -193,7 +212,7 @@
val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
R.layout.desktop_mode_app_header
} else {
- R.layout.desktop_mode_app_header
+ R.layout.desktop_mode_app_handle
}
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
onClickListener, onTouchListener, appIcon, appName, displayController,
@@ -208,5 +227,11 @@
private val FREEFORM_BOUNDS = Rect(500, 500, 2000, 1200)
private val SPLIT_LEFT_BOUNDS = Rect(0, 0, 1280, 1600)
private val SPLIT_RIGHT_BOUNDS = Rect(1280, 0, 2560, 1600)
+ private const val MENU_WIDTH = 200
+ private const val MENU_HEIGHT = 400
+ private const val MENU_TOP_MARGIN = 10
+ private const val MENU_START_MARGIN = 20
+ private const val MENU_PILL_ELEVATION = 2
+ private const val MENU_PILL_SPACING_MARGIN = 4
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 31c6479..2d1bf14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -54,6 +56,8 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
@@ -71,6 +75,7 @@
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -80,6 +85,7 @@
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -105,6 +111,9 @@
private static final int CORNER_RADIUS = 20;
private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -260,7 +269,8 @@
eq(0 /* index */),
eq(WindowInsets.Type.captionBar()),
eq(new Rect(100, 300, 400, 364)),
- any());
+ any(),
+ anyInt());
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -565,9 +575,9 @@
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -654,9 +664,9 @@
// Never added.
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
// No need to remove them if they were never added.
verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token),
any(), eq(0) /* index */, eq(captionBar()));
@@ -681,9 +691,9 @@
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
windowDecor.close();
@@ -738,9 +748,9 @@
// Insets should be applied twice.
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -765,9 +775,30 @@
// Insets should only need to be applied once.
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void testRelayout_captionInsetForceConsume() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+ final WindowContainerToken token = TestRunningTaskInfoBuilder.createMockWCToken();
+ final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true);
+
+ final ActivityManager.RunningTaskInfo taskInfo =
+ builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ windowDecor.relayout(taskInfo);
+
+ // Caption inset source should be force-consuming.
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
}
@Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 341599e..e302fa8 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -114,16 +114,12 @@
"libbase",
"libharfbuzz_ng",
"libminikin",
- "server_configurable_flags",
- "libaconfig_storage_read_api_cc"
],
static_libs: [
"libui-types",
],
- whole_static_libs: ["hwui_flags_cc_lib"],
-
target: {
android: {
shared_libs: [
@@ -145,6 +141,8 @@
"libsync",
"libui",
"aconfig_text_flags_c_lib",
+ "server_configurable_flags",
+ "libaconfig_storage_read_api_cc",
],
static_libs: [
"libEGL_blobCache",
@@ -155,6 +153,7 @@
"libstatssocket_lazy",
"libtonemap",
],
+ whole_static_libs: ["hwui_flags_cc_lib"],
},
host: {
static_libs: [
@@ -419,7 +418,6 @@
],
static_libs: [
- "libnativehelper_lazy",
"libziparchive_for_incfs",
],
@@ -446,6 +444,7 @@
],
static_libs: [
"libgif",
+ "libnativehelper_lazy",
"libstatslog_hwui",
"libstatspull_lazy",
"libstatssocket_lazy",
@@ -464,6 +463,7 @@
],
static_libs: [
"libandroidfw",
+ "libnativehelper_jvm",
],
},
},
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 70a9ef0..b4e6b72 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -28,6 +28,7 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
+extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -41,6 +42,7 @@
extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
extern int register_android_graphics_YuvImage(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env);
namespace android {
@@ -51,7 +53,12 @@
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
extern int register_android_graphics_PathIterator(JNIEnv* env);
@@ -72,6 +79,7 @@
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
#define REG_JNI(name) { name }
struct RegJNIRec {
@@ -83,6 +91,8 @@
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
{"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
+ {"android.graphics.BitmapRegionDecoder",
+ REG_JNI(register_android_graphics_BitmapRegionDecoder)},
{"android.graphics.ByteBufferStreamAdaptor",
REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
{"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
@@ -95,11 +105,20 @@
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
{"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+ {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+ {"android.graphics.HardwareBufferRenderer",
+ REG_JNI(register_android_graphics_HardwareBufferRenderer)},
+ {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)},
+ {"android.graphics.HardwareRendererObserver",
+ REG_JNI(register_android_graphics_HardwareRendererObserver)},
{"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
{"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
{"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
+ {"android.graphics.Mesh", REG_JNI(register_android_graphics_Mesh)},
+ {"android.graphics.MeshSpecification",
+ REG_JNI(register_android_graphics_MeshSpecification)},
{"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
{"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
{"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
@@ -118,6 +137,8 @@
REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
{"android.graphics.animation.RenderNodeAnimator",
REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
+ {"android.graphics.drawable.AnimatedImageDrawable",
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable)},
{"android.graphics.drawable.AnimatedVectorDrawable",
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
{"android.graphics.drawable.VectorDrawable",
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index cfca480..0efb2c8 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -17,7 +17,6 @@
#include <SkFontMetrics.h>
#include <SkRRect.h>
#include <SkTextBlob.h>
-#include <com_android_graphics_hwui_flags.h>
#include "../utils/Color.h"
#include "Canvas.h"
@@ -30,7 +29,19 @@
#include "hwui/PaintFilter.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
namespace flags = com::android::graphics::hwui::flags;
+#else
+namespace flags {
+constexpr bool high_contrast_text_luminance() {
+ return false;
+}
+constexpr bool high_contrast_text_small_text_rect() {
+ return false;
+}
+} // namespace flags
+#endif
namespace android {
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index ae9792d..b943496 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -126,7 +126,7 @@
SkSafeUnref(meshSpec);
}
-static jlong getMeshSpecificationFinalizer() {
+static jlong getMeshSpecificationFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
}
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index e3cdee6..3b1b861 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -135,7 +135,7 @@
proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
}
-static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy));
}
diff --git a/location/Android.bp b/location/Android.bp
index c0e102a..bc02d1f 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -30,9 +30,6 @@
"app-compat-annotations",
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
],
- hidden_api_packages: [
- "com.android.internal.location",
- ],
aidl: {
include_dirs: [
"frameworks/base/location/java",
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/location/java/com/android/internal/location/package-info.java
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to location/java/com/android/internal/location/package-info.java
index c92234c..25573c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/location/java/com/android/internal/location/package-info.java
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+/**
+ * Exclude from API surfaces
+ *
+ * @hide
+ */
+package com.android.internal.location;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d148afd..52b5ff7 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2649,4 +2649,11 @@
* @hide
*/
public static native boolean isBluetoothVariableLatencyEnabled();
+
+ /**
+ * Register a native listener for system property sysprop
+ * @param callback the listener which fires when the property changes
+ * @hide
+ */
+ public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 73deb17..03cd535 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1797,6 +1797,7 @@
(flags != 0 // cannot have any special flags
|| attributes.getUsage() != AudioAttributes.USAGE_MEDIA
|| (attributes.getContentType() != AudioAttributes.CONTENT_TYPE_UNKNOWN
+ && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_SPEECH
&& attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MUSIC
&& attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MOVIE))) {
return false;
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4059291..999f40e5 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -312,6 +312,10 @@
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
* {@link Surface}).
+ *
+ * <p>After this callback any call to
+ * {@link MediaProjection#createVirtualDisplay} will fail, even if no such
+ * {@link VirtualDisplay} was ever created for this MediaProjection session.
*/
public void onStop() { }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7ed67dc..4013d84 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -43,25 +43,31 @@
/**
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
*
- * <p><ol>An example flow of starting a media projection will be:
- * <li>Declare a foreground service with the type {@code mediaProjection} in
- * the {@code AndroidManifest.xml}.
- * </li>
- * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()}
- * and pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
- * </li>
- * <li>On getting {@link Activity#onActivityResult(int, int, Intent)},
- * start the foreground service with the type
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
- * </li>
- * <li>Retrieve the media projection token by calling
- * {@link MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and
- * intent from the {@link Activity#onActivityResult(int, int, Intent)} above.
- * </li>
- * <li>Start the screen capture session for media projection by calling
- * {@link MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * android.hardware.display.VirtualDisplay.Callback, Handler)}.
- * </li>
+ * <p>
+ *
+ * <ol>
+ * An example flow of starting a media projection will be:
+ * <li>Declare a foreground service with the type {@code mediaProjection} in the {@code
+ * AndroidManifest.xml}.
+ * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()} and
+ * pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
+ * <li>On getting {@link Activity#onActivityResult(int, int, Intent)}, start the foreground
+ * service with the type {@link
+ * android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ * <li>Retrieve the media projection token by calling {@link
+ * MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and intent
+ * from the {@link Activity#onActivityResult(int, int, Intent)} above.
+ * <li>Register a {@link MediaProjection.Callback} by calling {@link
+ * MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
+ * receive notifications about when the {@link MediaProjection} or captured content changes
+ * state. When receiving an `onStop()` callback, the client must clean up any resources it is
+ * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
+ * further no longer create any new {@link VirtualDisplay}s via {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)}.
+ * <li>Start the screen capture session for media projection by calling {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * android.hardware.display.VirtualDisplay.Callback, Handler)}.
* </ol>
*/
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
index 7065e3a..46c7273 100644
--- a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -27,6 +27,7 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
+import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -35,12 +36,13 @@
/** Utils for audio tests. */
public class TestUtils {
/**
- * Return a future for an intent delivered by a broadcast receiver which matches an
- * action and predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches an action and
+ * predicate.
+ *
* @param context - Context to register the receiver with
* @param action - String representing action to register receiver for
- * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
- * the future unset. If the predicate throws, the future is set exceptionally
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
* @return - The future representing intent delivery matching predicate.
*/
public static ListenableFuture<Intent> getFutureForIntent(
@@ -76,20 +78,77 @@
}
/**
- * Same as previous, but with no predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches one of a set of
+ * actions and predicate.
+ *
+ * @param context - Context to register the receiver with
+ * @param actionsCollection - Collection of actions which to listen for, completing on any
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
+ * @return - The future representing intent delivery matching predicate.
*/
+ public static ListenableFuture<Intent> getFutureForIntent(
+ Context context, Collection<String> actionsCollection, Predicate<Intent> pred) {
+ // These are evaluated async
+ Objects.requireNonNull(actionsCollection);
+ Objects.requireNonNull(pred);
+ if (actionsCollection.isEmpty()) {
+ throw new IllegalArgumentException("actionsCollection must not be empty");
+ }
+ return getFutureForListener(
+ (recv) ->
+ context.registerReceiver(
+ recv,
+ actionsCollection.stream()
+ .reduce(
+ new IntentFilter(),
+ (IntentFilter filter, String x) -> {
+ filter.addAction(x);
+ return filter;
+ },
+ (x, y) -> {
+ throw new IllegalStateException(
+ "No parallel support");
+ }),
+ Context.RECEIVER_EXPORTED),
+ (recv) -> {
+ try {
+ context.unregisterReceiver(recv);
+ } catch (IllegalArgumentException e) {
+ // Thrown when receiver is already unregistered, nothing to do
+ }
+ },
+ (completer) ->
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ if (actionsCollection.contains(intent.getAction())
+ && pred.test(intent)) {
+ completer.set(intent);
+ }
+ } catch (Exception e) {
+ completer.setException(e);
+ }
+ }
+ },
+ "Intent receiver future for actions: " + actionsCollection);
+ }
+
+ /** Same as previous, but with no predicate. */
public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
return getFutureForIntent(context, action, i -> true);
}
/**
* Return a future for a callback registered to a listener interface.
+ *
* @param registerFunc - Function which consumes the callback object for registration
- * @param unregisterFunc - Function which consumes the callback object for unregistration
- * This function is called when the future is completed or cancelled
+ * @param unregisterFunc - Function which consumes the callback object for unregistration This
+ * function is called when the future is completed or cancelled
* @param instantiateCallback - Factory function for the callback object, provided a completer
- * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
- * to the future returned by this function
+ * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
+ * to the future returned by this function
* @param debug - Debug string contained in future {@code toString} representation.
*/
public static <T, V> ListenableFuture<T> getFutureForListener(
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 13ac231..0282e6f 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -61,9 +61,6 @@
"android.nfc",
"com.android.nfc",
],
- hidden_api_packages: [
- "com.android.nfc",
- ],
impl_library_visibility: [
"//frameworks/base:__subpackages__",
"//cts/hostsidetests/multidevices/nfc:__subpackages__",
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 9ce1c82..395f81d 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -963,22 +963,9 @@
throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ " NFC extras APIs");
}
- try {
- return sService.getNfcDtaInterface(mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return null;
- }
- try {
- return sService.getNfcDtaInterface(mContext.getPackageName());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return null;
- }
+ return callServiceReturn(() -> sService.getNfcDtaInterface(mContext.getPackageName()),
+ null);
+
}
/**
@@ -1095,22 +1082,8 @@
@SystemApi
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public @AdapterState int getAdapterState() {
- try {
- return sService.getState();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return NfcAdapter.STATE_OFF;
- }
- try {
- return sService.getState();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return NfcAdapter.STATE_OFF;
- }
+ return callServiceReturn(() -> sService.getState(), NfcAdapter.STATE_OFF);
+
}
/**
@@ -1134,22 +1107,8 @@
@FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean enable() {
- try {
- return sService.enable(mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.enable(mContext.getPackageName());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.enable(mContext.getPackageName()), false);
+
}
/**
@@ -1175,22 +1134,9 @@
@FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean disable() {
- try {
- return sService.disable(true, mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.disable(true, mContext.getPackageName());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.disable(true, mContext.getPackageName()),
+ false);
+
}
/**
@@ -1200,22 +1146,9 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean disable(boolean persist) {
- try {
- return sService.disable(persist, mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.disable(persist, mContext.getPackageName());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.disable(persist, mContext.getPackageName()),
+ false);
+
}
/**
@@ -1241,12 +1174,7 @@
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
public boolean isObserveModeSupported() {
- try {
- return sService.isObserveModeSupported();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
- }
+ return callServiceReturn(() -> sService.isObserveModeSupported(), false);
}
/**
@@ -1257,12 +1185,7 @@
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
public boolean isObserveModeEnabled() {
- try {
- return sService.isObserveModeEnabled();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
- }
+ return callServiceReturn(() -> sService.isObserveModeEnabled(), false);
}
/**
@@ -1286,12 +1209,8 @@
throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ " observe mode APIs");
}
- try {
- return sService.setObserveMode(enabled, mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
- }
+ return callServiceReturn(() -> sService.setObserveMode(enabled, mContext.getPackageName()),
+ false);
}
/**
@@ -2057,22 +1976,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.setNfcSecure(enable);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.setNfcSecure(enable);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.setNfcSecure(enable), false);
+
}
/**
@@ -2088,22 +1993,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.deviceSupportsNfcSecure();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.deviceSupportsNfcSecure();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.deviceSupportsNfcSecure(), false);
+
}
/**
@@ -2121,22 +2012,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.getNfcAntennaInfo();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return null;
- }
- try {
- return sService.getNfcAntennaInfo();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return null;
- }
+ return callServiceReturn(() -> sService.getNfcAntennaInfo(), null);
+
}
/**
@@ -2154,22 +2031,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isNfcSecureEnabled();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isNfcSecureEnabled();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isNfcSecureEnabled(), false);
+
}
/**
@@ -2185,22 +2048,8 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.enableReaderOption(enable);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.enableReaderOption(enable);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.enableReaderOption(enable), false);
+
}
/**
@@ -2214,22 +2063,8 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isReaderOptionSupported();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isReaderOptionSupported();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isReaderOptionSupported(), false);
+
}
/**
@@ -2245,22 +2080,8 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isReaderOptionEnabled();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isReaderOptionEnabled();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isReaderOptionEnabled(), false);
+
}
/**
@@ -2388,11 +2209,9 @@
synchronized (mLock) {
mTagRemovedListener = iListener;
}
- try {
- return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
- } catch (RemoteException e) {
- return false;
- }
+ final ITagRemovedCallback.Stub passedListener = iListener;
+ return callServiceReturn(() ->
+ sService.ignore(tag.getServiceHandle(), debounceMs, passedListener), false);
}
/**
@@ -2509,22 +2328,9 @@
throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ " NFC extras APIs");
}
- try {
- return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return null;
- }
- try {
- return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return null;
- }
+ return callServiceReturn(() ->
+ sService.getNfcAdapterExtrasInterface(mContext.getPackageName()), null);
+
}
void enforceResumed(Activity activity) {
@@ -2569,22 +2375,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.setControllerAlwaysOn(value);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.setControllerAlwaysOn(value);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.setControllerAlwaysOn(value), false);
+
}
/**
@@ -2600,22 +2392,8 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
public boolean isControllerAlwaysOn() {
- try {
- return sService.isControllerAlwaysOn();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isControllerAlwaysOn();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isControllerAlwaysOn(), false);
+
}
/**
@@ -2634,22 +2412,8 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isControllerAlwaysOnSupported();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isControllerAlwaysOnSupported();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isControllerAlwaysOnSupported(), false);
+
}
/**
@@ -2719,21 +2483,9 @@
Log.e(TAG, "TagIntentAppPreference is not supported");
throw new UnsupportedOperationException();
}
- try {
- return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- try {
- return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
- }
+ return callServiceReturn(() ->
+ sService.setTagIntentAppPreferenceForUser(userId, pkg, allow),
+ TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE);
}
@@ -2808,22 +2560,8 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isTagIntentAppPreferenceSupported();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isTagIntentAppPreferenceSupported();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isTagIntentAppPreferenceSupported(), false);
+
}
/**
@@ -2836,26 +2574,10 @@
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
- try {
- if (sService == null) {
- attemptDeadServiceRecovery(null);
- }
- sService.notifyPollingLoop(pollingFrame);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return;
- }
- try {
- sService.notifyPollingLoop(pollingFrame);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- }
+ callService(() -> sService.notifyPollingLoop(pollingFrame));
}
+
/**
* Notifies the system of new HCE data for tests.
*
@@ -2863,11 +2585,19 @@
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void notifyTestHceData(int technology, byte[] data) {
+ callService(() -> sService.notifyTestHceData(technology, data));
+ }
+
+ interface ServiceCall {
+ void call() throws RemoteException;
+ }
+
+ void callService(ServiceCall call) {
try {
if (sService == null) {
attemptDeadServiceRecovery(null);
}
- sService.notifyTestHceData(technology, data);
+ call.call();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
@@ -2876,12 +2606,36 @@
return;
}
try {
- sService.notifyTestHceData(technology, data);
- } catch (RemoteException e2) {
+ call.call();
+ } catch (RemoteException ee) {
Log.e(TAG, "Failed to recover NFC Service.");
}
}
}
+ interface ServiceCallReturn<T> {
+ T call() throws RemoteException;
+ }
+ <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ return call.call();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return defaultReturn;
+ }
+ try {
+ return call.call();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ return defaultReturn;
+ }
/**
* Notifies the system of a an HCE session being deactivated.
@@ -2891,24 +2645,7 @@
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void notifyHceDeactivated() {
- try {
- if (sService == null) {
- attemptDeadServiceRecovery(null);
- }
- sService.notifyHceDeactivated();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return;
- }
- try {
- sService.notifyHceDeactivated();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- }
+ callService(() -> sService.notifyHceDeactivated());
}
/**
@@ -2924,22 +2661,7 @@
if (!sHasNfcWlcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.setWlcEnabled(enable);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.setWlcEnabled(enable);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.setWlcEnabled(enable), false);
}
/**
@@ -2954,22 +2676,8 @@
if (!sHasNfcWlcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.isWlcEnabled();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- return sService.isWlcEnabled();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return false;
- }
+ return callServiceReturn(() -> sService.isWlcEnabled(), false);
+
}
/**
@@ -3048,22 +2756,8 @@
if (!sHasNfcWlcFeature) {
throw new UnsupportedOperationException();
}
- try {
- return sService.getWlcListenerDeviceInfo();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return null;
- }
- try {
- return sService.getWlcListenerDeviceInfo();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return null;
- }
+ return callServiceReturn(() -> sService.getWlcListenerDeviceInfo(), null);
+
}
/**
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 225f8c6..52e0cbb 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -31,3 +31,11 @@
description: "Deletes flag and settings resets"
bug: "333847376"
}
+
+flag {
+ name: "refactor_crashrecovery"
+ namespace: "modularization"
+ description: "Refactor required CrashRecovery code"
+ bug: "289203818"
+ is_fixed_read_only: true
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index c260426..88770d4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -206,7 +206,7 @@
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
- val restriction = getDevicePolicyRestrictions()
+ val restriction = getDevicePolicyRestrictions(isTrustedSource)
if (restriction != null) {
val adminSupportDetailsIntent =
devicePolicyManager!!.createAdminSupportIntent(restriction)
@@ -237,18 +237,25 @@
intent: Intent,
callingUid: Int,
): Boolean {
- val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
- return (sourceInfo != null && sourceInfo.isPrivilegedApp
- && (isNotUnknownSource
- || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)))
+ val isPrivilegedAndKnown = sourceInfo != null && sourceInfo.isPrivilegedApp &&
+ intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+ val isInstallPkgPermissionGranted =
+ isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)
+
+ return isPrivilegedAndKnown || isInstallPkgPermissionGranted
}
- private fun getDevicePolicyRestrictions(): String? {
- val restrictions = arrayOf(
- UserManager.DISALLOW_INSTALL_APPS,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
- )
+ private fun getDevicePolicyRestrictions(isTrustedSource: Boolean): String? {
+ val restrictions: Array<String> = if (isTrustedSource) {
+ arrayOf(UserManager.DISALLOW_INSTALL_APPS)
+ } else {
+ arrayOf(
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ )
+ }
+
for (restriction in restrictions) {
if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
continue
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index 8572852..828a95f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -130,8 +130,8 @@
* @param context the [Context] object
* @param callingUid the UID of the caller of Pia
* @param isTrustedSource indicates whether install request is coming from a privileged app
- * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has
- * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
+ * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or an app that
+ * has the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
*
* @return `true` if the package is either a system downloads provider, a document manager,
* a trusted source, or has declared the
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index bbf0315..4387b6f 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -16,6 +16,8 @@
package com.android.settingslib.widget;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -122,6 +124,8 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
+ R.id.illustration_frame);
final ImageView backgroundView =
(ImageView) holder.findViewById(R.id.background_view);
final FrameLayout middleGroundLayout =
@@ -130,15 +134,15 @@
(LottieAnimationView) holder.findViewById(R.id.lottie_view);
if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
illustrationView.setContentDescription(mContentDescription);
- illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ final View illustrationContainer = (View) illustrationFrame.getParent();
+ illustrationContainer.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// To solve the problem of non-compliant illustrations, we set the frame height
// to 300dp and set the length of the short side of the screen to
// the width of the frame.
final int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
final int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
- final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
- R.id.illustration_frame);
final LayoutParams lp = (LayoutParams) illustrationFrame.getLayoutParams();
lp.width = screenWidth < screenHeight ? screenWidth : screenHeight;
illustrationFrame.setLayoutParams(lp);
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index df5644b..2645360 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -46,20 +46,6 @@
</intent-filter>
</provider>
- <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
- android:authorities="com.android.spa.gallery.slice.provider"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.app.slice.category.SLICE" />
- </intent-filter>
- </provider>
-
- <receiver
- android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
- android:exported="false">
- </receiver>
-
<activity
android:name="com.android.settingslib.spa.debug.BlankActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 91bd791..ffd2879 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -55,7 +55,6 @@
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
-import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
/**
* Enum to define all SPP name here.
@@ -120,9 +119,7 @@
override val logger = DebugLogger()
override val browseActivityClass = GalleryMainActivity::class.java
- override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
// For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
- override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 96de1a7..6d1d346 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -35,10 +34,8 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -48,15 +45,10 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-import com.android.settingslib.spa.slice.provider.createDemoActionSlice
-import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
-import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
-import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -139,26 +131,6 @@
override val enabled = { model.asyncEnable.value }
}
)
- }
- .setSliceDataFn { sliceUri, _ ->
- val createSliceImpl = { s: String ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = ASYNC_PREFERENCE_TITLE,
- summary = s,
- )
- }
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(createSliceImpl("(loading)"))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "Async entry loading")
- delay(2000L)
- postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
- }
- }
}.build()
)
entryList.add(
@@ -176,28 +148,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoActionSlice(
- sliceUri = sliceUri,
- title = MANUAL_UPDATE_PREFERENCE_TITLE,
- summary = "manual update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncAction() {
- tick++
- postValue(createSliceImpl(tick))
- }
- }
}.build()
)
entryList.add(
@@ -216,33 +166,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = AUTO_UPDATE_PREFERENCE_TITLE,
- summary = "auto update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "autoUpdater.active")
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(createSliceImpl(tick))
- }
- }
- }
}.build()
)
@@ -272,22 +195,6 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
- .setSliceDataFn { sliceUri, _ ->
- val intent = owner.createIntent()?.createBrowsePendingIntent()
- ?: return@setSliceDataFn null
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(
- createDemoSlice(
- sliceUri = sliceUri,
- title = PAGE_TITLE,
- summary = "Injected Entry",
- intent = intent,
- )
- )
- }
- }
- }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 14af508..2965793 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.debug
-import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -41,8 +40,6 @@
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
import com.android.settingslib.spa.framework.util.createIntent
-import com.android.settingslib.spa.slice.fromEntry
-import com.android.settingslib.spa.slice.presenter.SliceDemo
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -52,7 +49,6 @@
private const val ROUTE_ROOT = "root"
private const val ROUTE_All_PAGES = "pages"
private const val ROUTE_All_ENTRIES = "entries"
-private const val ROUTE_All_SLICES = "slices"
private const val ROUTE_PAGE = "page"
private const val ROUTE_ENTRY = "entry"
private const val PARAM_NAME_PAGE_ID = "pid"
@@ -87,7 +83,6 @@
composable(route = ROUTE_ROOT) { RootPage() }
composable(route = ROUTE_All_PAGES) { AllPages() }
composable(route = ROUTE_All_ENTRIES) { AllEntries() }
- composable(route = ROUTE_All_SLICES) { AllSlices() }
composable(
route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
arguments = listOf(
@@ -109,8 +104,6 @@
val entryRepository by spaEnvironment.entryRepository
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
val allEntry = remember { entryRepository.getAllEntries() }
- val allSliceEntry =
- remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
HomeScaffold(title = "Settings Debug") {
Preference(object : PreferenceModel {
override val title = "List All Pages (${allPageWithEntry.size})"
@@ -120,10 +113,6 @@
override val title = "List All Entries (${allEntry.size})"
override val onClick = navigator(route = ROUTE_All_ENTRIES)
})
- Preference(object : PreferenceModel {
- override val title = "List All Slices (${allSliceEntry.size})"
- override val onClick = navigator(route = ROUTE_All_SLICES)
- })
}
}
@@ -152,18 +141,6 @@
}
}
- @Composable
- fun AllSlices() {
- val entryRepository by spaEnvironment.entryRepository
- val authority = spaEnvironment.sliceProviderAuthorities
- val allSliceEntry =
- remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
- RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
- for (entry in allSliceEntry) {
- SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
- }
- }
- }
@Composable
fun OnePage(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 444a3f0..06d105b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -70,7 +70,6 @@
"allowSearch = $isAllowSearch",
"isSearchDynamic = $isSearchDataDynamic",
"isSearchMutable = $hasMutableStatus",
- "hasSlice = $hasSliceSupport",
"------ SEARCH ------",
"search_path = $entryPathWithTitle",
searchData?.debugContent() ?: "no search data",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 2d956d5..6e5132b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,12 +17,10 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
-import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -69,8 +67,6 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
- val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
-
// The application context. Use local context as fallback when applicationContext is not
// available (e.g. in Robolectric test).
val appContext: Context = context.applicationContext ?: context
@@ -81,11 +77,9 @@
// Specify class name of browse activity and slice broadcast receiver, which is used to
// generate the necessary intents.
open val browseActivityClass: Class<out Activity>? = null
- open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
// Specify provider authorities for debugging purpose.
open val searchProviderAuthorities: String? = null
- open val sliceProviderAuthorities: String? = null
// TODO: add other environment setup here.
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
index 780933d3c..e5bbb8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -50,7 +50,6 @@
INTENT_TARGET_PACKAGE("intentTargetPackage"),
INTENT_TARGET_CLASS("intentTargetClass"),
INTENT_EXTRAS("intentExtras"),
- SLICE_URI("sliceUri"),
ENTRY_DISABLED("entryDisabled"),
}
@@ -71,7 +70,6 @@
ColumnEnum.INTENT_TARGET_PACKAGE,
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
- ColumnEnum.SLICE_URI,
)
),
SEARCH_DYNAMIC_DATA_QUERY(
@@ -85,7 +83,6 @@
ColumnEnum.INTENT_TARGET_PACKAGE,
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
- ColumnEnum.SLICE_URI,
)
),
SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
@@ -115,7 +112,6 @@
ColumnEnum.INTENT_TARGET_PACKAGE,
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
- ColumnEnum.SLICE_URI,
ColumnEnum.ENTRY_DISABLED,
)
),
@@ -130,7 +126,6 @@
ColumnEnum.INTENT_TARGET_PACKAGE,
ColumnEnum.INTENT_TARGET_CLASS,
ColumnEnum.INTENT_EXTRAS,
- ColumnEnum.SLICE_URI,
ColumnEnum.ENTRY_DISABLED,
)
),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index eacb28c..65f700c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -32,8 +32,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
import com.android.settingslib.spa.framework.util.createIntent
-import com.android.settingslib.spa.slice.fromEntry
-
private const val TAG = "SpaSearchProvider"
@@ -217,11 +215,6 @@
.add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
.add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
}
- if (entry.hasSliceSupport)
- row.add(
- ColumnEnum.SLICE_URI.id, Uri.Builder()
- .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
- )
}
private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
@@ -252,11 +245,6 @@
.add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
.add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
}
- if (entry.hasSliceSupport)
- row.add(
- ColumnEnum.SLICE_URI.id, Uri.Builder()
- .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
- )
// Fetch status data. We can add runtime arguments later if necessary
val statusData = entry.getStatusData() ?: return
row.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
deleted file mode 100644
index 7a4750d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
-import com.android.settingslib.spa.framework.util.getEntryId
-
-private const val TAG = "SliceDataRepository"
-
-class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
- // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
- private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
-
- // Note: mark this function synchronized, so that we can get the same livedata during the
- // whole lifecycle of a Slice.
- @Synchronized
- fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
- sliceDataMap[sliceString] = it
- it
- }
- }
-
- fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- val sliceData = sliceDataMap[sliceString] ?: return null
- return if (sliceData.isActive()) sliceData else null
- }
-
- private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
- Log.d(TAG, "buildLiveData: $sliceUri")
-
- val entryId = sliceUri.getEntryId() ?: return null
- val entry = entryRepository.getEntry(entryId) ?: return null
- if (!entry.hasSliceSupport) return null
- val arguments = sliceUri.getRuntimeArguments()
- return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index f362890..ec89c7c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -16,23 +16,10 @@
package com.android.settingslib.spa.slice
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
-import com.android.settingslib.spa.framework.util.SESSION_SLICE
-import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
-import com.android.settingslib.spa.framework.util.appendSpaParams
-import com.android.settingslib.spa.framework.util.getDestination
-import com.android.settingslib.spa.framework.util.getEntryId
// Defines SliceUri, which contains special query parameters:
// -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -45,25 +32,6 @@
return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
}
-fun SliceUri.getDestination(): String? {
- return getQueryParameter(KEY_DESTINATION)
-}
-
-fun SliceUri.getRuntimeArguments(): Bundle {
- val params = Bundle()
- for (queryName in queryParameterNames) {
- if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
- params.putString(queryName, getQueryParameter(queryName))
- }
- return params
-}
-
-fun SliceUri.getSliceId(): String? {
- val entryId = getEntryId() ?: return null
- val params = getRuntimeArguments()
- return "${entryId}_$params"
-}
-
fun Uri.Builder.appendSpaParams(
destination: String? = null,
entryId: String? = null,
@@ -79,72 +47,3 @@
return this
}
-fun Uri.Builder.fromEntry(
- entry: SettingsEntry,
- authority: String?,
- runtimeArguments: Bundle? = null
-): Uri.Builder {
- if (authority == null) return this
- val sp = entry.containerPage()
- return scheme("content").authority(authority).appendSpaParams(
- destination = sp.buildRoute(),
- entryId = entry.id,
- runtimeArguments = runtimeArguments
- )
-}
-
-fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val sliceBroadcastClass =
- SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
- val entryId = getEntryId() ?: return null
- return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
-}
-
-fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-fun Intent.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-private fun createBrowsePendingIntent(
- context: Context,
- browseActivityClass: Class<out Activity>,
- destination: String,
- entryId: String?
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
- .appendSpaParams(destination, entryId, SESSION_SLICE)
- .apply {
- // Set both extra and data (which is a Uri) in Slice Intent:
- // 1) extra is used in SPA navigation framework
- // 2) data is used in Slice framework
- data = Uri.Builder().appendSpaParams(destination, entryId).build()
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
-
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
-}
-
-private fun createBroadcastPendingIntent(
- context: Context,
- sliceBroadcastClass: Class<out BroadcastReceiver>,
- entryId: String
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
- .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
- return PendingIntent.getBroadcast(
- context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
deleted file mode 100644
index 39cb431..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-
-class SpaSliceBroadcastReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- val sliceUri = intent?.data ?: return
- val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
- sliceData.doAction()
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
deleted file mode 100644
index 3496f02..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.slice.SliceProvider
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-private const val TAG = "SpaSliceProvider"
-
-class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
- private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- return sliceRepository.getOrBuildSliceData(sliceUri)
- }
-
- override fun onBindSlice(sliceUri: Uri): Slice? {
- if (context == null) return null
- Log.d(TAG, "onBindSlice: $sliceUri")
- return getOrPutSliceData(sliceUri)?.value
- }
-
- override fun onSlicePinned(sliceUri: Uri) {
- Log.d(TAG, "onSlicePinned: $sliceUri")
- super.onSlicePinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.observeForever(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onSliceUnpinned(sliceUri: Uri) {
- Log.d(TAG, "onSliceUnpinned: $sliceUri")
- super.onSliceUnpinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.removeObserver(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onChanged(value: Slice?) {
- val uri = value?.uri ?: return
- Log.d(TAG, "onChanged: $uri")
- context?.contentResolver?.notifyChange(uri, null)
- }
-
- override fun onCreateSliceProvider(): Boolean {
- Log.d(TAG, "onCreateSliceProvider")
- return true
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
deleted file mode 100644
index 007f47b..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.presenter
-
-import android.net.Uri
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.slice.widget.SliceLiveData
-import androidx.slice.widget.SliceView
-
-@Composable
-fun SliceDemo(sliceUri: Uri) {
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val sliceData = remember {
- SliceLiveData.fromUri(context, sliceUri)
- }
-
- HorizontalDivider()
- AndroidView(
- factory = { localContext ->
- val view = SliceView(localContext)
- view.setShowTitleItems(true)
- view.isScrollable = false
- view
- },
- update = { view -> sliceData.observe(lifecycleOwner, view) }
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
deleted file mode 100644
index e4a7386..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice.provider
-
-import android.app.PendingIntent
-import android.content.Context
-import android.net.Uri
-import androidx.core.R
-import androidx.core.graphics.drawable.IconCompat
-import androidx.slice.Slice
-import androidx.slice.SliceManager
-import androidx.slice.builders.ListBuilder
-import androidx.slice.builders.SliceAction
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.slice.createBroadcastPendingIntent
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-
-fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBrowsePendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBroadcastPendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
- val context = SpaEnvironmentFactory.instance.appContext
- if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
- return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
- .addRow(ListBuilder.RowBuilder().apply {
- setPrimaryAction(createSliceAction(context, intent))
- setTitle(title)
- setSubtitle(summary)
- }).build()
-}
-
-private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
- return SliceAction.create(
- intent,
- IconCompat.createWithResource(context, R.drawable.notification_action_background),
- ListBuilder.ICON_IMAGE,
- "Enter app"
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
deleted file mode 100644
index 341a4a5..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.slice
-
-import android.content.Context
-import android.net.Uri
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.util.genEntryId
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SppHome
-import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsSliceDataRepositoryTest {
- @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment =
- SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
- private val sliceDataRepository by spaEnvironment.sliceDataRepository
-
- @Test
- fun getOrBuildSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Slice empty
- assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
-
- // Slice supported
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
- assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
- assertThat(sliceData).isNotNull()
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // Slice unsupported
- val entryId2 = genEntryId("Layer2Entry2", page)
- val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
- assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
- }
-
- @Test
- fun getActiveSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
-
- // build slice data first
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
-
- // slice data is inactive
- assertThat(sliceData!!.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
-
- // slice data is active
- val observer = Observer<Slice?> { }
- sliceData.observeForever(observer)
- assertThat(sliceData.isActive()).isTrue()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // slice data is inactive again
- sliceData.removeObserver(observer)
- assertThat(sliceData.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
- }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index d1c4e51..b489afd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -16,91 +16,27 @@
package com.android.settingslib.spa.slice
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import androidx.core.os.bundleOf
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliceUtilTest {
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
-
@Test
fun sliceUriTest() {
assertThat(Uri.EMPTY.getEntryId()).isNull()
- assertThat(Uri.EMPTY.getDestination()).isNull()
- assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(Uri.EMPTY.getSliceId()).isNull()
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
- assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
- }
-
- @Test
- fun createBroadcastPendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isBroadcast).isTrue()
- assertThat(pendingIntent.isImmutable).isFalse()
- }
-
- @Test
- fun createBrowsePendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
-
- // Empty Intent
- assertThat(Intent().createBrowsePendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUri.createBrowsePendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isActivity).isTrue()
- assertThat(pendingIntent.isImmutable).isTrue()
-
- // Valid Intent
- val intent = Intent().apply {
- putExtra("spaActivityDestination", dest)
- putExtra("highlightEntry", entryId)
- }
- val pendingIntent2 = intent.createBrowsePendingIntent()
- assertThat(pendingIntent2).isNotNull()
- assertThat(pendingIntent2!!.isActivity).isTrue()
- assertThat(pendingIntent2.isImmutable).isTrue()
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 22a5ca3..4f8fd79 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -216,8 +216,6 @@
context: Context,
rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
- override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
- BlankSliceBroadcastReceiver::class.java,
override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index b949cd5..ac0b9b4 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.drawer;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,7 +26,9 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -36,6 +39,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -102,6 +107,9 @@
/** The key used to get the package name of the icon resource for the preference. */
static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
+ /** The key used for the raw byte data of the icon for the preference. */
+ static final String EXTRA_PREFERENCE_ICON_RAW = "com.android.settings.icon_raw";
+
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the key that should be used for the preference.
@@ -518,6 +526,24 @@
}
/**
+ * Retrieves an icon stored in the Bundle as a Parcel with key EXTRA_PREFERENCE_ICON_RAW
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Nullable
+ public static Icon getRawIconFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return null;
+ }
+ final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (bundle == null) {
+ return null;
+ }
+
+ return bundle.getParcelable(EXTRA_PREFERENCE_ICON_RAW, Icon.class);
+ }
+
+ /**
* Gets text associated with the input key from the content provider.
*
* @param context context
@@ -564,8 +590,9 @@
return getBundleFromUri(context, uri, providerMap, bundle);
}
- private static Bundle getBundleFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap, Bundle bundle) {
+ @Nullable
+ private static Bundle getBundleFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap, @Nullable Bundle bundle) {
final Pair<String, String> args = getMethodAndKey(uri);
if (args == null) {
return null;
@@ -593,8 +620,9 @@
}
}
- private static IContentProvider getProviderFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap) {
+ @Nullable
+ private static IContentProvider getProviderFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
if (uri == null) {
return null;
}
@@ -609,7 +637,8 @@
}
/** Returns method and key of the complete uri. */
- private static Pair<String, String> getMethodAndKey(Uri uri) {
+ @Nullable
+ private static Pair<String, String> getMethodAndKey(@Nullable Uri uri) {
if (uri == null) {
return null;
}
diff --git a/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
index 6552296..bc5ec69 100644
--- a/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
+++ b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml
@@ -28,7 +28,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <com.android.settingslib.notification.ZenRadioLayout
+ <com.android.settingslib.notification.modes.ZenRadioLayout
android:id="@+id/zen_duration_conditions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -46,7 +46,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"/>
- </com.android.settingslib.notification.ZenRadioLayout>
+ </com.android.settingslib.notification.modes.ZenRadioLayout>
</LinearLayout>
</ScrollView>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index f89fe93..67139b5 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -14,8 +14,7 @@
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources>
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
<color name="bt_color_icon_1">#b4a50e0e</color> <!-- 72% Material Red 900 -->
@@ -34,8 +33,8 @@
<color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 -->
<color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 -->
- <color name="black">@*android:color/black</color>
- <color name="white">@*android:color/white</color>
+ <color name="dark_mode_icon_color_single_tone">#99000000</color>
+ <color name="light_mode_icon_color_single_tone">#ffffff</color>
<color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 6c2bd41..ef0f6cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -100,9 +100,9 @@
mCutoutHeightFraction = context.getResources().getFloat(
com.android.internal.R.dimen.config_signalCutoutHeightFraction);
mDarkModeFillColor = Utils.getColorStateListDefaultColor(context,
- R.color.black);
+ R.color.dark_mode_icon_color_single_tone);
mLightModeFillColor = Utils.getColorStateListDefaultColor(context,
- R.color.white);
+ R.color.light_mode_icon_color_single_tone);
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mTransparentPaint.setColor(context.getColor(android.R.color.transparent));
mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 6571dd7..eced7b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -29,6 +29,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -41,7 +42,6 @@
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -65,7 +65,9 @@
refreshDevices();
};
- private final AtomicReference<MediaRouter2.ScanToken> mScanToken = new AtomicReference<>();
+ @GuardedBy("this")
+ @Nullable
+ private MediaRouter2.ScanToken mScanToken;
// TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
/* package */ RouterInfoMediaManager(
@@ -101,8 +103,13 @@
@Override
protected void startScanOnRouter() {
if (Flags.enableScreenOffScanning()) {
- MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build();
- mScanToken.compareAndSet(null, mRouter.requestScan(request));
+ synchronized (this) {
+ if (mScanToken == null) {
+ MediaRouter2.ScanRequest request =
+ new MediaRouter2.ScanRequest.Builder().build();
+ mScanToken = mRouter.requestScan(request);
+ }
+ }
} else {
mRouter.startScan();
}
@@ -120,9 +127,11 @@
@Override
protected void stopScanOnRouter() {
if (Flags.enableScreenOffScanning()) {
- MediaRouter2.ScanToken token = mScanToken.getAndSet(null);
- if (token != null) {
- mRouter.cancelScanRequest(token);
+ synchronized (this) {
+ if (mScanToken != null) {
+ mRouter.cancelScanRequest(mScanToken);
+ mScanToken = null;
+ }
}
} else {
mRouter.stopScan();
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 8868837..4028b73 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -105,12 +105,6 @@
public static final String COLUMN_CARD_STATE = "cardState";
/**
- * The name of the extended APDU supported state column, see
- * {@link UiccSlotInfo#getIsExtendedApduSupported()}.
- */
- public static final String COLUMN_IS_EXTENDED_APDU_SUPPORTED = "isExtendedApduSupported";
-
- /**
* The name of the removable state column, see {@link UiccSlotInfo#isRemovable()}.
*/
public static final String COLUMN_IS_REMOVABLE = "isRemovable";
@@ -150,74 +144,17 @@
public static final String COLUMN_SIM_SLOT_INDEX = "simSlotIndex";
/**
- * The name of the carrier ID column, see {@link SubscriptionInfo#getCarrierId()}.
- */
- public static final String COLUMN_CARRIER_ID = "carrierId";
-
- /**
- * The name of the display name column, see {@link SubscriptionInfo#getDisplayName()}.
- */
- public static final String COLUMN_DISPLAY_NAME = "displayName";
-
- /**
- * The name of the carrier name column, see {@link SubscriptionInfo#getCarrierName()}.
- */
- public static final String COLUMN_CARRIER_NAME = "carrierName";
-
- /**
- * The name of the data roaming state column, see
- * {@link SubscriptionInfo#getDataRoaming()}.
- */
- public static final String COLUMN_DATA_ROAMING = "dataRoaming";
-
- /**
- * The name of the mcc column, see {@link SubscriptionInfo#getMccString()}.
- */
- public static final String COLUMN_MCC = "mcc";
-
- /**
- * The name of the mnc column, see {@link SubscriptionInfo#getMncString()}.
- */
- public static final String COLUMN_MNC = "mnc";
-
- /**
- * The name of the country ISO column, see {@link SubscriptionInfo#getCountryIso()}.
- */
- public static final String COLUMN_COUNTRY_ISO = "countryIso";
-
- /**
* The name of the embedded state column, see {@link SubscriptionInfo#isEmbedded()}.
*/
public static final String COLUMN_IS_EMBEDDED = "isEmbedded";
/**
- * The name of the card ID column, see {@link SubscriptionInfo#getCardId()}.
- */
- public static final String COLUMN_CARD_ID = "cardId";
-
- /**
- * The name of the port index column, see {@link SubscriptionInfo#getPortIndex()}.
- */
- public static final String COLUMN_PORT_INDEX = "portIndex";
-
- /**
* The name of the opportunistic state column, see
* {@link SubscriptionInfo#isOpportunistic()}.
*/
public static final String COLUMN_IS_OPPORTUNISTIC = "isOpportunistic";
/**
- * The name of the groupUUID column, see {@link SubscriptionInfo#getGroupUuid()}.
- */
- public static final String COLUMN_GROUP_UUID = "groupUUID";
-
- /**
- * The name of the subscription type column, see
- * {@link SubscriptionInfo#getSubscriptionType()}}.
- */
- public static final String COLUMN_SUBSCRIPTION_TYPE = "subscriptionType";
-
- /**
* The name of the uniqueName column,
* {@see SubscriptionUtil#getUniqueSubscriptionDisplayName(SubscriptionInfo, Context)}.
*/
@@ -231,19 +168,6 @@
public static final String COLUMN_IS_SUBSCRIPTION_VISIBLE = "isSubscriptionVisible";
/**
- * The name of the formatted phone number column,
- * {@see SubscriptionUtil#getFormattedPhoneNumber(Context, SubscriptionInfo)}.
- */
- public static final String COLUMN_FORMATTED_PHONE_NUMBER = "getFormattedPhoneNumber";
-
- /**
- * The name of the first removable subscription state column,
- * {@see SubscriptionUtil#getFirstRemovableSubscription(Context)}.
- */
- public static final String COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION =
- "isFirstRemovableSubscription";
-
- /**
* The name of the default subscription selection column,
* {@see SubscriptionUtil#getSubscriptionOrDefault(Context, int)}.
*/
@@ -257,24 +181,12 @@
public static final String COLUMN_IS_VALID_SUBSCRIPTION = "isValidSubscription";
/**
- * The name of the usable subscription column,
- * {@link SubscriptionManager#isUsableSubscriptionId(int)}.
- */
- public static final String COLUMN_IS_USABLE_SUBSCRIPTION = "isUsableSubscription";
-
- /**
* The name of the active subscription column,
* {@link SubscriptionManager#isActiveSubscriptionId(int)}.
*/
public static final String COLUMN_IS_ACTIVE_SUBSCRIPTION_ID = "isActiveSubscription";
/**
- * The name of the available subscription column,
- * {@see SubscriptionUtil#getAvailableSubscription(Context, ProxySubscriptionManager, int)}.
- */
- public static final String COLUMN_IS_AVAILABLE_SUBSCRIPTION = "isAvailableSubscription";
-
- /**
* The name of the active data subscription state column, see
* {@link SubscriptionManager#getActiveDataSubscriptionId()}.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
index e6b1cfb..060eab66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
@@ -40,15 +40,6 @@
+ DataServiceUtils.SubscriptionInfoData.COLUMN_ID + " = :subId")
SubscriptionInfoEntity querySubInfoById(String subId);
- @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
- + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID
- + " = :isActiveSubscription" + " AND "
- + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE
- + " = :isSubscriptionVisible" + " ORDER BY "
- + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
- LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos(
- boolean isActiveSubscription, boolean isSubscriptionVisible);
-
@Query("SELECT COUNT(*) FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME)
int count();
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 361a2461..88e6a57 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -19,7 +19,6 @@
import android.text.TextUtils;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@@ -28,39 +27,19 @@
@Entity(tableName = DataServiceUtils.SubscriptionInfoData.TABLE_NAME)
public class SubscriptionInfoEntity {
- public SubscriptionInfoEntity(@NonNull String subId, int simSlotIndex, int carrierId,
- String displayName, String carrierName, int dataRoaming, String mcc, String mnc,
- String countryIso, boolean isEmbedded, int cardId, int portIndex,
- boolean isOpportunistic, @Nullable String groupUUID, int subscriptionType,
- String uniqueName, boolean isSubscriptionVisible, @Nullable String formattedPhoneNumber,
- boolean isFirstRemovableSubscription, boolean isDefaultSubscriptionSelection,
- boolean isValidSubscription, boolean isUsableSubscription,
- boolean isActiveSubscriptionId, boolean isAvailableSubscription,
- boolean isActiveDataSubscriptionId) {
+ public SubscriptionInfoEntity(@NonNull String subId, int simSlotIndex, boolean isEmbedded,
+ boolean isOpportunistic, String uniqueName, boolean isSubscriptionVisible,
+ boolean isDefaultSubscriptionSelection, boolean isValidSubscription,
+ boolean isActiveSubscriptionId, boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
- this.carrierId = carrierId;
- this.displayName = displayName;
- this.carrierName = carrierName;
- this.dataRoaming = dataRoaming;
- this.mcc = mcc;
- this.mnc = mnc;
- this.countryIso = countryIso;
this.isEmbedded = isEmbedded;
- this.cardId = cardId;
- this.portIndex = portIndex;
this.isOpportunistic = isOpportunistic;
- this.groupUUID = groupUUID;
- this.subscriptionType = subscriptionType;
this.uniqueName = uniqueName;
this.isSubscriptionVisible = isSubscriptionVisible;
- this.formattedPhoneNumber = formattedPhoneNumber;
- this.isFirstRemovableSubscription = isFirstRemovableSubscription;
this.isDefaultSubscriptionSelection = isDefaultSubscriptionSelection;
this.isValidSubscription = isValidSubscription;
- this.isUsableSubscription = isUsableSubscription;
this.isActiveSubscriptionId = isActiveSubscriptionId;
- this.isAvailableSubscription = isAvailableSubscription;
this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@@ -72,59 +51,18 @@
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
public int simSlotIndex;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARRIER_ID)
- public int carrierId;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DISPLAY_NAME)
- public String displayName;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARRIER_NAME)
- public String carrierName;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DATA_ROAMING)
- public int dataRoaming;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_MCC)
- public String mcc;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_MNC)
- public String mnc;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_COUNTRY_ISO)
- public String countryIso;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_EMBEDDED)
public boolean isEmbedded;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARD_ID)
- public int cardId;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_PORT_INDEX)
- public int portIndex;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_OPPORTUNISTIC)
public boolean isOpportunistic;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_GROUP_UUID)
- @Nullable
- public String groupUUID;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_SUBSCRIPTION_TYPE)
- public int subscriptionType;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_UNIQUE_NAME)
public String uniqueName;
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE)
public boolean isSubscriptionVisible;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_FORMATTED_PHONE_NUMBER)
- @Nullable
- public String formattedPhoneNumber;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION)
- public boolean isFirstRemovableSubscription;
-
@ColumnInfo(name =
DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION)
public boolean isDefaultSubscriptionSelection;
@@ -132,15 +70,9 @@
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_VALID_SUBSCRIPTION)
public boolean isValidSubscription;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_USABLE_SUBSCRIPTION)
- public boolean isUsableSubscription;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID)
public boolean isActiveSubscriptionId;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_AVAILABLE_SUBSCRIPTION)
- public boolean isAvailableSubscription;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
public boolean isActiveDataSubscriptionId;
@@ -165,28 +97,13 @@
return Objects.hash(
subId,
simSlotIndex,
- carrierId,
- displayName,
- carrierName,
- dataRoaming,
- mcc,
- mnc,
- countryIso,
isEmbedded,
- cardId,
- portIndex,
isOpportunistic,
- groupUUID,
- subscriptionType,
uniqueName,
isSubscriptionVisible,
- formattedPhoneNumber,
- isFirstRemovableSubscription,
isDefaultSubscriptionSelection,
isValidSubscription,
- isUsableSubscription,
isActiveSubscriptionId,
- isAvailableSubscription,
isActiveDataSubscriptionId);
}
@@ -202,28 +119,13 @@
SubscriptionInfoEntity info = (SubscriptionInfoEntity) obj;
return TextUtils.equals(subId, info.subId)
&& simSlotIndex == info.simSlotIndex
- && carrierId == info.carrierId
- && TextUtils.equals(displayName, info.displayName)
- && TextUtils.equals(carrierName, info.carrierName)
- && dataRoaming == info.dataRoaming
- && TextUtils.equals(mcc, info.mcc)
- && TextUtils.equals(mnc, info.mnc)
- && TextUtils.equals(countryIso, info.countryIso)
&& isEmbedded == info.isEmbedded
- && cardId == info.cardId
- && portIndex == info.portIndex
&& isOpportunistic == info.isOpportunistic
- && TextUtils.equals(groupUUID, info.groupUUID)
- && subscriptionType == info.subscriptionType
&& TextUtils.equals(uniqueName, info.uniqueName)
&& isSubscriptionVisible == info.isSubscriptionVisible
- && TextUtils.equals(formattedPhoneNumber, info.formattedPhoneNumber)
- && isFirstRemovableSubscription == info.isFirstRemovableSubscription
&& isDefaultSubscriptionSelection == info.isDefaultSubscriptionSelection
&& isValidSubscription == info.isValidSubscription
- && isUsableSubscription == info.isUsableSubscription
&& isActiveSubscriptionId == info.isActiveSubscriptionId
- && isAvailableSubscription == info.isAvailableSubscription
&& isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
@@ -233,50 +135,20 @@
.append(subId)
.append(", simSlotIndex = ")
.append(simSlotIndex)
- .append(", carrierId = ")
- .append(carrierId)
- .append(", displayName = ")
- .append(displayName)
- .append(", carrierName = ")
- .append(carrierName)
- .append(", dataRoaming = ")
- .append(dataRoaming)
- .append(", mcc = ")
- .append(mcc)
- .append(", mnc = ")
- .append(mnc)
- .append(", countryIso = ")
- .append(countryIso)
.append(", isEmbedded = ")
.append(isEmbedded)
- .append(", cardId = ")
- .append(cardId)
- .append(", portIndex = ")
- .append(portIndex)
.append(", isOpportunistic = ")
.append(isOpportunistic)
- .append(", groupUUID = ")
- .append(groupUUID)
- .append(", subscriptionType = ")
- .append(subscriptionType)
.append(", uniqueName = ")
.append(uniqueName)
.append(", isSubscriptionVisible = ")
.append(isSubscriptionVisible)
- .append(", formattedPhoneNumber = ")
- .append(formattedPhoneNumber)
- .append(", isFirstRemovableSubscription = ")
- .append(isFirstRemovableSubscription)
.append(", isDefaultSubscriptionSelection = ")
.append(isDefaultSubscriptionSelection)
.append(", isValidSubscription = ")
.append(isValidSubscription)
- .append(", isUsableSubscription = ")
- .append(isUsableSubscription)
.append(", isActiveSubscriptionId = ")
.append(isActiveSubscriptionId)
- .append(", isAvailableSubscription = ")
- .append(isAvailableSubscription)
.append(", isActiveDataSubscriptionId = ")
.append(isActiveDataSubscriptionId)
.append(")}");
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 7f6a8ed..7886e85 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -18,6 +18,9 @@
import android.app.NotificationManager
import android.provider.Settings
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -32,6 +35,11 @@
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ MutableStateFlow(listOf(TestModeBuilder.EXAMPLE))
+ override val modes: Flow<List<ZenMode>>
+ get() = mutableModesFlow.asStateFlow()
+
init {
updateNotificationPolicy()
}
@@ -43,6 +51,20 @@
fun updateZenMode(zenMode: Int) {
mutableZenMode.value = zenMode
}
+
+ fun addMode(id: String, active: Boolean = false) {
+ mutableModesFlow.value += newMode(id, active)
+ }
+
+ fun removeMode(id: String) {
+ mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
+ }
+
+ fun deactivateMode(id: String) {
+ val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
+ removeMode(id)
+ mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ }
}
fun FakeZenModeRepository.updateNotificationPolicy(
@@ -61,5 +83,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- )
- )
+ ))
+
+private fun newMode(id: String, active: Boolean = false): ZenMode {
+ return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 273a63d..26b5741 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -17,19 +17,28 @@
package com.android.settingslib.notification.data.repository
import android.app.NotificationManager
+import android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -44,11 +53,16 @@
/** @see NotificationManager.getZenMode */
val globalZenMode: StateFlow<Int?>
+
+ /** A list of all existing priority modes. */
+ val modes: Flow<List<ZenMode>>
}
class ZenModeRepositoryImpl(
private val context: Context,
private val notificationManager: NotificationManager,
+ private val backend: ZenModesBackend,
+ private val contentResolver: ContentResolver,
val scope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
@@ -56,12 +70,12 @@
val backgroundHandler: Handler?,
) : ZenModeRepository {
- private val notificationBroadcasts =
+ private val notificationBroadcasts by lazy {
callbackFlow {
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- intent?.action?.let { action -> launch { send(action) } }
+ intent?.let { launch { send(it) } }
}
}
@@ -87,7 +101,6 @@
.let {
if (Flags.volumePanelBroadcastFix()) {
it.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
} else {
it.shareIn(
started = SharingStarted.WhileSubscribed(),
@@ -95,27 +108,73 @@
)
}
}
+ }
- override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> =
+ override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
- notificationManager.consolidatedNotificationPolicy
+ // If available, get the value from extras to avoid a potential binder call.
+ it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
+ ?: notificationManager.consolidatedNotificationPolicy
}
else
flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
notificationManager.consolidatedNotificationPolicy
}
+ }
- override val globalZenMode: StateFlow<Int?> =
+ override val globalZenMode: StateFlow<Int?> by lazy {
flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) {
notificationManager.zenMode
}
+ }
- private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
+ private fun <T> flowFromBroadcast(intentAction: String, mapper: (Intent?) -> T) =
notificationBroadcasts
- .filter { intentAction == it }
- .map { mapper() }
- .onStart { emit(mapper()) }
+ .filter { intentAction == it.action }
+ .map { mapper(it) }
+ .onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val zenConfigChanged by lazy {
+ if (android.app.Flags.modesUi()) {
+ callbackFlow {
+ // emit an initial value
+ trySend(Unit)
+
+ val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ /* notifyForDescendants= */ false,
+ observer)
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
+ /* notifyForDescendants= */ false,
+ observer)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(Unit)
+ }
+ }
+
+ override val modes: Flow<List<ZenMode>> by lazy {
+ if (android.app.Flags.modesUi()) {
+ zenConfigChanged
+ .map { backend.modes }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(emptyList())
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
new file mode 100644
index 0000000..7b994d5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -0,0 +1,176 @@
+/*
+ * 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.settingslib.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import java.util.Random;
+
+public class TestModeBuilder {
+
+ private String mId;
+ private AutomaticZenRule mRule;
+ private ZenModeConfig.ZenRule mConfigZenRule;
+
+ public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+
+ public TestModeBuilder() {
+ // Reasonable defaults
+ int id = new Random().nextInt(1000);
+ mId = "rule_" + id;
+ mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
+ .setPackage("some_package")
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build();
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.pkg = "some_package";
+ }
+
+ public TestModeBuilder(ZenMode previous) {
+ mId = previous.getId();
+ mRule = previous.getRule();
+
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = previous.getRule().isEnabled();
+ mConfigZenRule.pkg = previous.getRule().getPackageName();
+ setActive(previous.isActive());
+ }
+
+ public TestModeBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public TestModeBuilder setAzr(AutomaticZenRule rule) {
+ mRule = rule;
+ mConfigZenRule.pkg = rule.getPackageName();
+ mConfigZenRule.conditionId = rule.getConditionId();
+ mConfigZenRule.enabled = rule.isEnabled();
+ return this;
+ }
+
+ public TestModeBuilder setConfigZenRule(ZenModeConfig.ZenRule configZenRule) {
+ mConfigZenRule = configZenRule;
+ return this;
+ }
+
+ public TestModeBuilder setName(String name) {
+ mRule.setName(name);
+ mConfigZenRule.name = name;
+ return this;
+ }
+
+ public TestModeBuilder setPackage(String pkg) {
+ mRule.setPackageName(pkg);
+ mConfigZenRule.pkg = pkg;
+ return this;
+ }
+
+ public TestModeBuilder setOwner(ComponentName owner) {
+ mRule.setOwner(owner);
+ mConfigZenRule.component = owner;
+ return this;
+ }
+
+ public TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
+ mRule.setConfigurationActivity(configActivity);
+ mConfigZenRule.configurationActivity = configActivity;
+ return this;
+ }
+
+ public TestModeBuilder setConditionId(Uri conditionId) {
+ mRule.setConditionId(conditionId);
+ mConfigZenRule.conditionId = conditionId;
+ return this;
+ }
+
+ public TestModeBuilder setType(@AutomaticZenRule.Type int type) {
+ mRule.setType(type);
+ mConfigZenRule.type = type;
+ return this;
+ }
+
+ public TestModeBuilder setInterruptionFilter(
+ @NotificationManager.InterruptionFilter int interruptionFilter) {
+ mRule.setInterruptionFilter(interruptionFilter);
+ mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ return this;
+ }
+
+ public TestModeBuilder setZenPolicy(@Nullable ZenPolicy policy) {
+ mRule.setZenPolicy(policy);
+ mConfigZenRule.zenPolicy = policy;
+ return this;
+ }
+
+ public TestModeBuilder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mRule.setDeviceEffects(deviceEffects);
+ mConfigZenRule.zenDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled) {
+ mRule.setEnabled(enabled);
+ mConfigZenRule.enabled = enabled;
+ return this;
+ }
+
+ public TestModeBuilder setManualInvocationAllowed(boolean allowed) {
+ mRule.setManualInvocationAllowed(allowed);
+ mConfigZenRule.allowManualInvocation = allowed;
+ return this;
+ }
+
+ public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ mRule.setTriggerDescription(triggerDescription);
+ mConfigZenRule.triggerDescription = triggerDescription;
+ return this;
+ }
+
+ public TestModeBuilder setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ return this;
+ }
+
+ public TestModeBuilder setActive(boolean active) {
+ if (active) {
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.condition = new Condition(mRule.getConditionId(), "...",
+ Condition.STATE_TRUE);
+ } else {
+ mConfigZenRule.condition = null;
+ }
+ return this;
+ }
+
+ public ZenMode build() {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 2cc9112..03a2b75 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -286,11 +286,7 @@
mRule, oldCondition, conditionId));
}
- public boolean canEditName() {
- return !isManualDnd();
- }
-
- public boolean canEditIcon() {
+ public boolean canEditNameAndIcon() {
return !isManualDnd();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 096c25d..4bd5cc4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -18,13 +18,19 @@
import android.app.NotificationManager
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
+import android.database.ContentObserver
+import android.os.Parcelable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -36,6 +42,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -48,13 +55,20 @@
@RunWith(RobolectricTestRunner::class)
@SmallTest
class ZenModeRepositoryTest {
-
@Mock private lateinit var context: Context
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var zenModesBackend: ZenModesBackend
+
+ @Mock private lateinit var contentResolver: ContentResolver
+
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var zenModeObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor private lateinit var zenConfigObserverCaptor: ArgumentCaptor<ContentObserver>
+
private lateinit var underTest: ZenModeRepository
private val testScope: TestScope = TestScope()
@@ -67,13 +81,15 @@
ZenModeRepositoryImpl(
context,
notificationManager,
+ zenModesBackend,
+ contentResolver,
testScope.backgroundScope,
testScope.testScheduler,
backgroundHandler = null,
)
}
- @DisableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
@Test
fun consolidatedPolicyChanges_repositoryEmits_flagsOff() {
testScope.runTest {
@@ -88,9 +104,7 @@
triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
runCurrent()
- assertThat(values)
- .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
- .inOrder()
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
}
}
@@ -109,9 +123,27 @@
triggerIntent(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
runCurrent()
- assertThat(values)
- .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
- .inOrder()
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
+ }
+ }
+
+ @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @Test
+ fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
+ testScope.runTest {
+ val values = mutableListOf<NotificationManager.Policy?>()
+ `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
+ underTest.consolidatedNotificationPolicy
+ .onEach { values.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED,
+ extras = mapOf(NotificationManager.EXTRA_NOTIFICATION_POLICY to testPolicy2))
+ runCurrent()
+
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
}
}
@@ -128,15 +160,68 @@
runCurrent()
assertThat(values)
- .containsExactlyElementsIn(
- listOf(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS))
+ .containsExactly(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS)
.inOrder()
}
}
- private fun triggerIntent(action: String) {
- verify(context).registerReceiver(receiverCaptor.capture(), any())
- receiverCaptor.value.onReceive(context, Intent(action))
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ @Test
+ fun modesListEmitsOnSettingsChange() {
+ testScope.runTest {
+ val values = mutableListOf<List<ZenMode>>()
+ val modes1 = listOf(TestModeBuilder().setId("One").build())
+ `when`(zenModesBackend.modes).thenReturn(modes1)
+ underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ // zen mode change triggers update
+ val modes2 = listOf(TestModeBuilder().setId("Two").build())
+ `when`(zenModesBackend.modes).thenReturn(modes2)
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ // zen config change also triggers update
+ val modes3 = listOf(TestModeBuilder().setId("Three").build())
+ `when`(zenModesBackend.modes).thenReturn(modes3)
+ triggerZenConfigSettingUpdate()
+ runCurrent()
+
+ // setting update with no list change doesn't trigger update
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ }
+ }
+
+ private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
+ val intent = Intent(action)
+ if (extras?.isNotEmpty() == true) {
+ extras.forEach { (key, value) -> intent.putExtra(key, value) }
+ }
+ receiverCaptor.value.onReceive(context, intent)
+ }
+
+ private fun triggerZenModeSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE)),
+ eq(false),
+ zenModeObserverCaptor.capture(),
+ )
+ zenModeObserverCaptor.value.onChange(false)
+ }
+
+ private fun triggerZenConfigSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG)),
+ eq(false),
+ zenConfigObserverCaptor.capture(),
+ )
+ zenConfigObserverCaptor.value.onChange(false)
}
private companion object {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 9e54312..e705f97 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -56,12 +56,14 @@
assertThat(zenMode.getId()).isEqualTo("id");
assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
assertThat(zenMode.isManualDnd()).isFalse();
+ assertThat(zenMode.canEditNameAndIcon()).isTrue();
assertThat(zenMode.canBeDeleted()).isTrue();
assertThat(zenMode.isActive()).isTrue();
ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
assertThat(manualMode.isManualDnd()).isTrue();
+ assertThat(manualMode.canEditNameAndIcon()).isFalse();
assertThat(manualMode.canBeDeleted()).isFalse();
assertThat(manualMode.isActive()).isFalse();
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 2227943..8b0772b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -42,6 +42,7 @@
import android.provider.UpdatableDeviceConfigServiceReadiness;
import android.util.Slog;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.util.FastPrintWriter;
import java.io.File;
@@ -416,7 +417,13 @@
DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case OVERRIDE:
- DeviceConfig.setLocalOverride(namespace, key, value);
+ AconfigFlags.Permission permission =
+ (new AconfigFlags()).getFlagPermission(key);
+ if (permission == AconfigFlags.Permission.READ_ONLY) {
+ pout.println("cannot override read-only flag " + key);
+ } else {
+ DeviceConfig.setLocalOverride(namespace, key, value);
+ }
break;
case CLEAR_OVERRIDE:
DeviceConfig.clearLocalOverride(namespace, key);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 8dd51b2..8de0c35 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,8 +22,6 @@
import android.content.ContentResolver;
import android.os.Bundle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -73,24 +71,9 @@
}
/**
- * Test that setting overrides are properly disabled when the flag is off.
- */
- @Test
- @RequiresFlagsDisabled("com.android.providers.settings.support_overrides")
- public void testOverrideDisabled() throws IOException {
- final String newValue = "value2";
-
- executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue);
- executeShellCommand("device_config override " + sNamespace + " " + sKey + " " + newValue);
- String result = readShellCommandOutput("device_config get " + sNamespace + " " + sKey);
- assertEquals(sValue + "\n", result);
- }
-
- /**
* Test that overrides are readable and can be cleared.
*/
@Test
- @RequiresFlagsEnabled("com.android.providers.settings.support_overrides")
public void testOverride() throws IOException {
final String newValue = "value2";
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9cbb1bd..1871873 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -581,7 +581,6 @@
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
- "device_policy_aconfig_flags_lib",
],
libs: [
"keepanno-annotations",
@@ -669,6 +668,7 @@
],
asset_dirs: [
"tests/goldens",
+ "schemas",
],
static_libs: [
"SystemUI-res",
@@ -708,6 +708,7 @@
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
"androidx.room_room-runtime",
+ "androidx.room_room-testing",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
"device_state_flags_lib",
@@ -789,7 +790,11 @@
"android.test.mock",
"keepanno-annotations",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ // TODO(b/352363800): Why do we need this?
+ "-J-Xmx8192M",
+ ],
aaptflags: [
"--extra-packages",
"com.android.systemui",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e2ecda3..71f5511 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -591,6 +591,16 @@
}
flag {
+ name: "screenshot_save_image_exporter"
+ namespace: "systemui"
+ description: "Save all screenshots using ImageExporter"
+ bug: "352308052"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1143,3 +1153,37 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "systemui"
+ name: "qs_register_setting_observer_on_bg_thread"
+ description: "Registers Quick Settings content providers on background thread"
+ bug: "351766769"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "sounddose_customization"
+ namespace: "systemui"
+ description: "Enables custom actions for sounddose notifications"
+ bug: "345227709"
+}
+
+flag {
+ namespace: "systemui"
+ name: "register_content_observers_async"
+ description: "Use new Async API to register content observers"
+ bug: "316922634"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "msdl_feedback"
+ namespace: "systemui"
+ description: "Enables MSDL feedback in SysUI surfaces."
+ bug: "352600066"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index b8f9ca8..f655ac1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -83,6 +83,7 @@
import com.android.compose.PlatformButton
import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -516,13 +517,22 @@
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- SceneTransitionLayout(
- currentScene = currentSceneKey,
- onChangeScene = {},
- transitions = SceneTransitions,
- modifier = modifier,
- enableInterruptions = false,
- ) {
+ val state = remember {
+ MutableSceneTransitionLayoutState(
+ currentSceneKey,
+ SceneTransitions,
+ enableInterruptions = false,
+ )
+ }
+
+ // Update state whenever currentSceneKey has changed.
+ LaunchedEffect(state, currentSceneKey) {
+ if (currentSceneKey != state.transitionState.currentScene) {
+ state.setTargetScene(currentSceneKey, coroutineScope = this)
+ }
+ }
+
+ SceneTransitionLayout(state, modifier = modifier) {
scene(SceneKeys.ContiguousSceneKey) {
FoldableScene(
aboveFold = aboveFold,
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 b4a4412..70b3efc 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
@@ -42,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -66,6 +67,7 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@@ -96,6 +98,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -175,7 +178,11 @@
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
- val gridState = rememberLazyGridState()
+
+ val gridState =
+ rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset)
+ viewModel.clearPersistedScrollPosition()
+
val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle()
val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle()
@@ -191,6 +198,8 @@
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
+ ObserveScrollEffect(gridState, viewModel)
+
if (!viewModel.isEditMode) {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
@@ -428,6 +437,20 @@
}
}
+@Composable
+private fun ObserveScrollEffect(
+ gridState: LazyGridState,
+ communalViewModel: BaseCommunalViewModel
+) {
+
+ LaunchedEffect(gridState) {
+ snapshotFlow {
+ Pair(gridState.firstVisibleItemIndex, gridState.firstVisibleItemScrollOffset)
+ }
+ .collect { communalViewModel.onScrollPositionUpdated(it.first, it.second) }
+ }
+}
+
/**
* Observes communal content and scrolls to any added or updated live content, e.g. a new media
* session is started, or a paused timer is resumed.
@@ -950,9 +973,25 @@
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
+
+ val isSelected = selectedKey == model.key
+
+ val selectableModifier =
+ if (viewModel.isEditMode) {
+ Modifier.selectable(
+ selected = isSelected,
+ onClick = { viewModel.setSelectedKey(model.key) },
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ )
+ } else {
+ Modifier
+ }
+
Box(
modifier =
modifier
+ .then(selectableModifier)
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 1ea73e1..620892a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -23,6 +23,8 @@
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,11 +41,16 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -54,6 +61,7 @@
import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.delay
class CommunalPopupSection
@Inject
@@ -91,6 +99,17 @@
onClick: () -> Unit,
onDismissRequest: () -> Unit,
) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val focusRequester = remember { FocusRequester() }
+
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) {
+ // Adding a delay to ensure the animation completes before requesting focus
+ delay(250)
+ focusRequester.requestFocus()
+ }
+
Popup(
alignment = Alignment.TopCenter,
offset = IntOffset(0, 40),
@@ -100,6 +119,8 @@
Button(
modifier =
Modifier.height(56.dp)
+ .focusRequester(focusRequester)
+ .focusable(interactionSource = interactionSource)
.graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
.animateEnterExit(
enter =
@@ -142,8 +163,7 @@
) {
Icon(
imageVector = Icons.Outlined.Widgets,
- contentDescription =
- stringResource(R.string.button_to_configure_widgets_text),
+ contentDescription = null,
tint = colors.onSecondary,
modifier = Modifier.size(20.dp)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 0a4c6fd..7400711 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -67,8 +67,7 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val shouldUseSplitNotificationShade by
- viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+ val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
@@ -100,7 +99,7 @@
}
)
}
- if (shouldUseSplitNotificationShade) {
+ if (isShadeLayoutWide) {
with(notificationSection) {
Notifications(
burnInParams = null,
@@ -112,7 +111,7 @@
}
}
}
- if (!shouldUseSplitNotificationShade) {
+ if (!isShadeLayoutWide) {
with(notificationSection) {
Notifications(
burnInParams = null,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 065f2a2..72cf832 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -69,8 +69,7 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val shouldUseSplitNotificationShade by
- viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+ val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
@@ -102,7 +101,7 @@
},
)
}
- if (shouldUseSplitNotificationShade) {
+ if (isShadeLayoutWide) {
with(notificationSection) {
Notifications(
burnInParams = null,
@@ -114,7 +113,7 @@
}
}
}
- if (!shouldUseSplitNotificationShade) {
+ if (!isShadeLayoutWide) {
with(notificationSection) {
Notifications(
burnInParams = null,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 7f80dfa..859c036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -90,10 +90,16 @@
*/
@Composable
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
- val shouldUseSplitNotificationShade by
- lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
val areNotificationsVisible by
- lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
+ lockscreenContentViewModel
+ .areNotificationsVisible(sceneKey)
+ .collectAsStateWithLifecycle(initialValue = false)
+ if (!areNotificationsVisible) {
+ return
+ }
+
+ val isShadeLayoutWide by
+ lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
if (Flags.centralizedStatusBarHeightFix()) {
LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -101,19 +107,13 @@
dimensionResource(id = R.dimen.large_screen_shade_header_height)
}
- if (!areNotificationsVisible) {
- return
- }
-
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
viewModel = viewModel,
modifier =
modifier
.fillMaxWidth()
- .thenIf(shouldUseSplitNotificationShade) {
- Modifier.padding(top = splitShadeTopMargin)
- }
+ .thenIf(isShadeLayoutWide) { Modifier.padding(top = splitShadeTopMargin) }
.let {
if (burnInParams == null) {
it
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index 166aa70..f9e2252 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -28,7 +28,6 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
@@ -83,29 +82,20 @@
componentFactory.build(view, provider).keyguardStatusBarViewController
}
- MovableElement(
- key = StatusBarElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = {
- notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
- (it.parent as ViewGroup).removeView(it)
- }
+ AndroidView(
+ factory = {
+ notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ }
- viewController.init()
- view
- },
- modifier =
- Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
- Utils.getStatusBarHeaderHeightKeyguard(context)
- },
- update = { viewController.setDisplayCutout(viewDisplayCutout) }
- )
- }
- }
+ viewController.init()
+ view
+ },
+ modifier =
+ Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ },
+ update = { viewController.setDisplayCutout(viewDisplayCutout) }
+ )
}
}
-
-private val StatusBarElementKey = ElementKey("StatusBar")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 0673153..0cd4b68 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -26,6 +26,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -33,6 +34,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.thenIf
@@ -78,13 +80,22 @@
WeatherClockScenes.splitShadeLargeClockScene
}
- SceneTransitionLayout(
- modifier = modifier,
- currentScene = currentScene,
- onChangeScene = {},
- transitions = ClockTransition.defaultClockTransitions,
- enableInterruptions = false,
- ) {
+ val state = remember {
+ MutableSceneTransitionLayoutState(
+ currentScene,
+ ClockTransition.defaultClockTransitions,
+ enableInterruptions = false,
+ )
+ }
+
+ // Update state whenever currentSceneKey has changed.
+ LaunchedEffect(state, currentScene) {
+ if (currentScene != state.transitionState.currentScene) {
+ state.setTargetScene(currentScene, coroutineScope = this)
+ }
+ }
+
+ SceneTransitionLayout(state, modifier) {
scene(splitShadeLargeClockScene) {
LargeClockWithSmartSpace(
shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 581f3a5..d629eec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -16,8 +16,10 @@
package com.android.systemui.media.controls.ui.composable
+import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
@@ -26,7 +28,6 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.contains
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -36,7 +37,8 @@
private object MediaCarousel {
object Elements {
- internal val Content = ElementKey("MediaCarouselContent")
+ internal val Content =
+ ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
}
}
@@ -61,40 +63,43 @@
mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
- AndroidView(
- modifier =
- modifier
- .element(MediaCarousel.Elements.Content)
- .height(mediaHeight)
- .fillMaxWidth()
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
+ MovableElement(
+ key = MediaCarousel.Elements.Content,
+ modifier = modifier.height(mediaHeight).fillMaxWidth()
+ ) {
+ content {
+ AndroidView(
+ modifier =
+ Modifier.fillMaxSize().layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
- // Notify controller to size the carousel for the current space
- mediaHost.measurementInput = MeasurementInput(placeable.width, placeable.height)
- carouselController.setSceneContainerSize(placeable.width, placeable.height)
+ // Notify controller to size the carousel for the current space
+ mediaHost.measurementInput =
+ MeasurementInput(placeable.width, placeable.height)
+ carouselController.setSceneContainerSize(placeable.width, placeable.height)
- layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+ layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+ },
+ factory = { context ->
+ FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+ }
},
- factory = { context ->
- FrameLayout(context).apply {
- val mediaFrame = carouselController.mediaFrame
- (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
- addView(mediaFrame)
- layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- )
- }
- },
- update = {
- if (it.contains(carouselController.mediaFrame)) {
- return@AndroidView
- }
- val mediaFrame = carouselController.mediaFrame
- (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
- it.addView(mediaFrame)
- },
- )
+ update = { it.setView(carouselController.mediaFrame) },
+ onRelease = { it.removeAllViews() }
+ )
+ }
+ }
+}
+
+private fun ViewGroup.setView(view: View) {
+ if (view.parent == this) {
+ return
+ }
+ (view.parent as? ViewGroup)?.removeView(view)
+ addView(view)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
new file mode 100644
index 0000000..0398133
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.media.controls.ui.composable
+
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementScenePicker
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+import com.android.systemui.scene.shared.model.Scenes
+
+/** [ElementScenePicker] implementation for the media carousel object. */
+object MediaScenePicker : ElementScenePicker {
+
+ private val shadeLockscreenFraction = 0.65f
+ private val scenes =
+ setOf(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ Scenes.QuickSettings,
+ Scenes.QuickSettingsShade,
+ Scenes.Communal
+ )
+
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey? {
+ return when {
+ // TODO: 352052894 - update with the actual scene picking
+ transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
+ if (transition.progress < shadeLockscreenFraction) {
+ Scenes.Lockscreen
+ } else {
+ Scenes.Shade
+ }
+ }
+
+ // TODO: 345467290 - update with the actual scene picking
+ transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
+ if (transition.progress < 1f - shadeLockscreenFraction) {
+ Scenes.Shade
+ } else {
+ Scenes.Lockscreen
+ }
+ }
+
+ // TODO: 345467290 - update with the actual scene picking
+ transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
+ Scenes.QuickSettings
+ }
+
+ // TODO: 340216785 - update with the actual scene picking
+ else -> pickSingleSceneIn(scenes, transition, element)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..776e166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -198,6 +198,7 @@
LaunchedEffect(scrollableState.isScrollInProgress) {
if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+ viewModel.setHeadsUpAnimatingAway(false)
viewModel.snoozeHun()
}
}
@@ -337,7 +338,7 @@
// expanded, reset scrim offset.
LaunchedEffect(stackHeight, scrimOffset) {
snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
- .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
}
// if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 4a6599a..805351e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -34,9 +34,11 @@
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
@@ -51,6 +53,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
@@ -60,6 +63,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.UserAction
@@ -255,6 +259,8 @@
val mediaOffset by
animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false)
+ val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
+
Box(
modifier =
modifier.thenIf(shouldPunchHoleBehindScrim) {
@@ -358,11 +364,22 @@
notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
}
}
- NotificationStackCutoffGuideline(
- stackScrollView = notificationStackScrollView,
- viewModel = notificationsPlaceholderViewModel,
- modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding()
- )
+ Box(
+ modifier =
+ Modifier.align(Alignment.BottomCenter)
+ .height(navBarHeight)
+ .pointerInteropFilter { true }
+ .verticalNestedScrollToScene(
+ topBehavior = NestedScrollBehavior.EdgeAlways,
+ isExternalOverscrollGesture = { false }
+ )
+ ) {
+ NotificationStackCutoffGuideline(
+ stackScrollView = notificationStackScrollView,
+ viewModel = notificationsPlaceholderViewModel,
+ modifier = Modifier.align(Alignment.TopCenter)
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
deleted file mode 100644
index f514ab4..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.ui.composable
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun StatusBar(
- modifier: Modifier = Modifier,
-) {
- // TODO(b/272780101): implement.
- Row(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text("Status bar")
- }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index d95b388..20b1303 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -184,31 +184,33 @@
): Swipes {
val fromSource =
startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- fromScene.targetSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
+ layoutImpl.swipeSourceDetector
+ .source(
+ fromScene.targetSize,
+ position.round(),
+ layoutImpl.density,
+ orientation,
+ )
+ ?.resolve(layoutImpl.layoutDirection)
}
val upOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersDown,
fromSource = fromSource,
)
val downOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersDown,
fromSource = fromSource,
@@ -833,10 +835,10 @@
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
private class Swipes(
- val upOrLeft: Swipe?,
- val downOrRight: Swipe?,
- val upOrLeftNoSource: Swipe?,
- val downOrRightNoSource: Swipe?,
+ val upOrLeft: Swipe.Resolved?,
+ val downOrRight: Swipe.Resolved?,
+ val upOrLeftNoSource: Swipe.Resolved?,
+ val downOrRightNoSource: Swipe.Resolved?,
) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@
fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
val userActions = fromScene.userActions
- fun result(swipe: Swipe?): UserActionResult? {
+ fun result(swipe: Swipe.Resolved?): UserActionResult? {
return userActions[swipe ?: return null]
}
@@ -940,25 +942,27 @@
when {
amount < 0f -> {
val actionUpOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionUpOrLeft]
}
amount > 0f -> {
val actionDownOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionDownOrRight]
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index b0dc3a1..97c0cef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -21,14 +21,28 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
/** The edge of a [SceneTransitionLayout]. */
-enum class Edge : SwipeSource {
- Left,
- Right,
- Top,
- Bottom,
+enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
+ Top(resolveEdge = { Resolved.Top }),
+ Bottom(resolveEdge = { Resolved.Bottom }),
+ Left(resolveEdge = { Resolved.Left }),
+ Right(resolveEdge = { Resolved.Right }),
+ Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ override fun resolve(layoutDirection: LayoutDirection): Resolved {
+ return resolveEdge(layoutDirection)
+ }
+
+ enum class Resolved : SwipeSource.Resolved {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ }
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index cdcfc84..615d393 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -241,43 +241,50 @@
}
}
- awaitPointerEventScope {
- while (isActive) {
- try {
- detectDragGestures(
- orientation = orientation,
- startDragImmediately = startDragImmediately,
- onDragStart = { startedPosition, overSlop, pointersDown ->
- velocityTracker.resetTracking()
- onDragStarted(startedPosition, overSlop, pointersDown)
- },
- onDrag = { controller, change, amount ->
- velocityTracker.addPointerInputChange(change)
- controller.onDrag(amount)
- },
- onDragEnd = { controller ->
- val viewConfiguration = currentValueOf(LocalViewConfiguration)
- val maxVelocity =
- viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
- val velocity = velocityTracker.calculateVelocity(maxVelocity)
- controller.onStop(
- velocity =
- when (orientation) {
- Orientation.Horizontal -> velocity.x
- Orientation.Vertical -> velocity.y
- },
- canChangeScene = true,
- )
- },
- onDragCancel = { controller ->
- controller.onStop(velocity = 0f, canChangeScene = true)
- },
- swipeDetector = swipeDetector
- )
- } catch (exception: CancellationException) {
- // If the coroutine scope is active, we can just restart the drag cycle.
- if (!isActive) {
- throw exception
+ // The order is important here: we want to make sure that the previous PointerEventScope
+ // is initialized first. This ensures that the following PointerEventScope doesn't
+ // receive more events than the first one.
+ launch {
+ awaitPointerEventScope {
+ while (isActive) {
+ try {
+ detectDragGestures(
+ orientation = orientation,
+ startDragImmediately = startDragImmediately,
+ onDragStart = { startedPosition, overSlop, pointersDown ->
+ velocityTracker.resetTracking()
+ onDragStarted(startedPosition, overSlop, pointersDown)
+ },
+ onDrag = { controller, change, amount ->
+ velocityTracker.addPointerInputChange(change)
+ controller.onDrag(amount)
+ },
+ onDragEnd = { controller ->
+ val viewConfiguration = currentValueOf(LocalViewConfiguration)
+ val maxVelocity =
+ viewConfiguration.maximumFlingVelocity.let {
+ Velocity(it, it)
+ }
+ val velocity = velocityTracker.calculateVelocity(maxVelocity)
+ controller.onStop(
+ velocity =
+ when (orientation) {
+ Orientation.Horizontal -> velocity.x
+ Orientation.Vertical -> velocity.y
+ },
+ canChangeScene = true,
+ )
+ },
+ onDragCancel = { controller ->
+ controller.onStop(velocity = 0f, canChangeScene = true)
+ },
+ swipeDetector = swipeDetector
+ )
+ } catch (exception: CancellationException) {
+ // If the coroutine scope is active, we can just restart the drag cycle.
+ if (!isActive) {
+ throw exception
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index dfb8c49..734241e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -56,13 +56,7 @@
progress.collect { backEvent -> transition.dragProgress = backEvent.progress }
// Back gesture successful.
- transition.animateTo(
- if (state.canChangeScene(targetSceneForBack)) {
- targetSceneForBack
- } else {
- fromScene
- }
- )
+ transition.animateTo(targetSceneForBack)
} catch (e: CancellationException) {
// Back gesture cancelled.
transition.animateTo(fromScene)
@@ -105,12 +99,15 @@
return it
}
- currentScene = scene
+ if (scene != currentScene && state.transitionState == this && state.canChangeScene(scene)) {
+ currentScene = scene
+ }
+
val targetProgress =
- when (scene) {
+ when (currentScene) {
fromScene -> 0f
toScene -> 1f
- else -> error("scene $scene should be either $fromScene or $toScene")
+ else -> error("scene $currentScene should be either $fromScene or $toScene")
}
val animatable = Animatable(dragProgress).also { progressAnimatable = it }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
deleted file mode 100644
index b346a70..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.animation.scene
-
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-
-/**
- * Punch a hole in this node with the given [size], [offset] and [shape].
- *
- * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
- * This can be used to make content drawn below an opaque element visible. For example, if we have
- * [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
- * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big clock
- * time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be the
- * result.
- */
-@Stable
-fun Modifier.punchHole(
- size: () -> Size,
- offset: () -> Offset,
- shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleElement(size, offset, shape))
-
-/**
- * Punch a hole in this node using the bounds of [coords] and the given [shape].
- *
- * You can use [androidx.compose.ui.layout.onGloballyPositioned] to get the last coordinates of a
- * node.
- */
-@Stable
-fun Modifier.punchHole(
- coords: () -> LayoutCoordinates?,
- shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleWithBoundsElement(coords, shape))
-
-private data class PunchHoleElement(
- private val size: () -> Size,
- private val offset: () -> Offset,
- private val shape: Shape,
-) : ModifierNodeElement<PunchHoleNode>() {
- override fun create(): PunchHoleNode = PunchHoleNode(size, offset, { shape })
-
- override fun update(node: PunchHoleNode) {
- node.size = size
- node.offset = offset
- node.shape = { shape }
- }
-}
-
-private class PunchHoleNode(
- var size: () -> Size,
- var offset: () -> Offset,
- var shape: () -> Shape,
-) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {
- private var lastSize: Size = Size.Unspecified
- private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
- private var lastOutline: Outline? = null
-
- override fun MeasureScope.measure(
- measurable: Measurable,
- constraints: Constraints
- ): MeasureResult {
- return measurable.measure(constraints).run {
- layout(width, height) {
- placeWithLayer(0, 0) { compositingStrategy = CompositingStrategy.Offscreen }
- }
- }
- }
-
- override fun ContentDrawScope.draw() {
- drawContent()
-
- val holeSize = size()
- if (holeSize != Size.Zero) {
- val offset = offset()
- translate(offset.x, offset.y) { drawHole(holeSize) }
- }
- }
-
- private fun DrawScope.drawHole(size: Size) {
- if (shape == RectangleShape) {
- drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
- return
- }
-
- val outline =
- if (size == lastSize && layoutDirection == lastLayoutDirection) {
- lastOutline!!
- } else {
- val newOutline = shape().createOutline(size, layoutDirection, this)
- lastSize = size
- lastLayoutDirection = layoutDirection
- lastOutline = newOutline
- newOutline
- }
-
- drawOutline(
- outline,
- Color.Black,
- blendMode = BlendMode.DstOut,
- )
- }
-}
-
-private data class PunchHoleWithBoundsElement(
- private val coords: () -> LayoutCoordinates?,
- private val shape: Shape,
-) : ModifierNodeElement<PunchHoleWithBoundsNode>() {
- override fun create(): PunchHoleWithBoundsNode = PunchHoleWithBoundsNode(coords, shape)
-
- override fun update(node: PunchHoleWithBoundsNode) {
- node.holeCoords = coords
- node.shape = shape
- }
-}
-
-private class PunchHoleWithBoundsNode(
- var holeCoords: () -> LayoutCoordinates?,
- var shape: Shape,
-) : DelegatingNode(), DrawModifierNode, GlobalPositionAwareModifierNode {
- private val delegate = delegate(PunchHoleNode(::holeSize, ::holeOffset, ::shape))
- private var lastCoords: LayoutCoordinates? = null
-
- override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
- this.lastCoords = coordinates
- }
-
- override fun ContentDrawScope.draw() = with(delegate) { draw() }
-
- private fun holeSize(): Size {
- return holeCoords()?.size?.toSize() ?: Size.Zero
- }
-
- private fun holeOffset(): Offset {
- val holeCoords = holeCoords() ?: return Offset.Zero
- val lastCoords = lastCoords ?: error("draw() was called before onGloballyPositioned()")
- return lastCoords.localPositionOf(holeCoords, relativeToSource = Offset.Zero)
- }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 936f4ba..a49f1af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -37,7 +37,7 @@
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, UserActionResult>,
+ actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@
}
private fun checkValid(
- userActions: Map<UserAction, UserActionResult>
- ): Map<UserAction, UserActionResult> {
+ userActions: Map<UserAction.Resolved, UserActionResult>
+ ): Map<UserAction.Resolved, UserActionResult> {
userActions.forEach { (action, result) ->
if (key == result.toScene) {
error(
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 7c8fce8..0c467b1 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
@@ -28,10 +28,13 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.UserAction.Resolved
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -48,7 +51,6 @@
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param scenes the configuration of the different scenes of this layout.
- * @see updateSceneTransitionLayoutState
*/
@Composable
fun SceneTransitionLayout(
@@ -70,56 +72,6 @@
)
}
-/**
- * [SceneTransitionLayout] is a container that automatically animates its content whenever
- * [currentScene] changes, using the transitions defined in [transitions].
- *
- * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
- * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
- * you need support for swipe gestures, shared elements or transitions defined declaratively outside
- * UI code.
- *
- * @param currentScene the current scene
- * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
- * This is called when the user commits a transition to a new scene because of a [UserAction], for
- * instance by triggering back navigation or by swiping to a new scene.
- * @param transitions the definition of the transitions used to animate a change of scene.
- * @param swipeSourceDetector the source detector used to detect which source a swipe is started
- * from, if any.
- * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
- * intercepted, the progress value must be above the threshold, and below (1 - threshold).
- * @param scenes the configuration of the different scenes of this layout.
- */
-@Composable
-fun SceneTransitionLayout(
- currentScene: SceneKey,
- onChangeScene: (SceneKey) -> Unit,
- transitions: SceneTransitions,
- modifier: Modifier = Modifier,
- swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
- swipeDetector: SwipeDetector = DefaultSwipeDetector,
- @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
- scenes: SceneTransitionLayoutScope.() -> Unit,
-) {
- val state =
- updateSceneTransitionLayoutState(
- currentScene,
- onChangeScene,
- transitions,
- enableInterruptions = enableInterruptions,
- )
-
- SceneTransitionLayout(
- state,
- modifier,
- swipeSourceDetector,
- swipeDetector,
- transitionInterceptionThreshold,
- scenes,
- )
-}
-
interface SceneTransitionLayoutScope {
/**
* Add a scene to this layout, identified by [key].
@@ -395,34 +347,71 @@
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
/** An action performed by the user. */
-sealed interface UserAction {
+sealed class UserAction {
infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
return this to UserActionResult(toScene = scene)
}
+
+ /** Resolve this into a [Resolved] user action given [layoutDirection]. */
+ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [UserAction] that does not depend on the layout direction. */
+ internal sealed class Resolved
}
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-data object Back : UserAction
+data object Back : UserAction() {
+ override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved
+
+ internal object Resolved : UserAction.Resolved()
+}
/** The user swiped on the container. */
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
val fromSource: SwipeSource? = null,
-) : UserAction {
+) : UserAction() {
companion object {
val Left = Swipe(SwipeDirection.Left)
val Up = Swipe(SwipeDirection.Up)
val Right = Swipe(SwipeDirection.Right)
val Down = Swipe(SwipeDirection.Down)
+ val Start = Swipe(SwipeDirection.Start)
+ val End = Swipe(SwipeDirection.End)
}
+
+ override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
+ return Resolved(
+ direction = direction.resolve(layoutDirection),
+ pointerCount = pointerCount,
+ fromSource = fromSource?.resolve(layoutDirection),
+ )
+ }
+
+ /** A resolved [Swipe] that does not depend on the layout direction. */
+ internal data class Resolved(
+ val direction: SwipeDirection.Resolved,
+ val pointerCount: Int,
+ val fromSource: SwipeSource.Resolved?,
+ ) : UserAction.Resolved()
}
-enum class SwipeDirection(val orientation: Orientation) {
- Up(Orientation.Vertical),
- Down(Orientation.Vertical),
- Left(Orientation.Horizontal),
- Right(Orientation.Horizontal),
+enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
+ Up(resolve = { Resolved.Up }),
+ Down(resolve = { Resolved.Down }),
+ Left(resolve = { Resolved.Left }),
+ Right(resolve = { Resolved.Right }),
+ Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ /** A resolved [SwipeDirection] that does not depend on the layout direction. */
+ internal enum class Resolved(val orientation: Orientation) {
+ Up(Orientation.Vertical),
+ Down(Orientation.Vertical),
+ Left(Orientation.Horizontal),
+ Right(Orientation.Horizontal),
+ }
}
/**
@@ -437,6 +426,16 @@
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
+
+ /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
+ fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [SwipeSource] that does not depend on the layout direction. */
+ interface Resolved {
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+ }
}
interface SwipeSourceDetector {
@@ -511,11 +510,13 @@
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
val coroutineScope = rememberCoroutineScope()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
+ layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
@@ -526,7 +527,7 @@
// TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
// SnapshotStateMap anymore.
- layoutImpl.updateScenes(scenes)
+ layoutImpl.updateScenes(scenes, layoutDirection)
SideEffect {
if (state != layoutImpl.state) {
@@ -537,6 +538,7 @@
}
layoutImpl.density = density
+ layoutImpl.layoutDirection = layoutDirection
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
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 6095419..3e48c42 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
@@ -33,6 +33,7 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
+ internal var layoutDirection: LayoutDirection,
internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@
private set
init {
- updateScenes(builder)
+ updateScenes(builder, layoutDirection)
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@
return scenes[key] ?: error("Scene $key is not configured")
}
- internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+ internal fun updateScenes(
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ layoutDirection: LayoutDirection,
+ ) {
// Keep a reference of the current scenes. After processing [builder], the scenes that were
// not configured will be removed.
val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@
) {
scenesToRemove.remove(key)
+ val resolvedUserActions =
+ userActions.mapKeys { it.key.resolve(layoutDirection) }
val scene = scenes[key]
if (scene != null) {
// Update an existing scene.
scene.content = content
- scene.userActions = userActions
+ scene.userActions = resolvedUserActions
scene.zIndex = zIndex
} else {
// New scene.
@@ -176,7 +183,7 @@
key,
this@SceneTransitionLayoutImpl,
content,
- userActions,
+ resolvedUserActions,
zIndex,
)
}
@@ -213,7 +220,7 @@
@Composable
private fun BackHandler() {
val targetSceneForBack =
- scene(state.transitionState.currentScene).userActions[Back]?.toScene
+ scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 5b4fbf0..56c8752 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -22,13 +22,9 @@
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastFilter
@@ -38,14 +34,12 @@
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
* The state of a [SceneTransitionLayout].
*
* @see MutableSceneTransitionLayoutState
- * @see updateSceneTransitionLayoutState
*/
@Stable
sealed interface SceneTransitionLayoutState {
@@ -152,55 +146,6 @@
)
}
-/**
- * Sets up a [SceneTransitionLayoutState] and keeps it synced with [currentScene], [onChangeScene]
- * and [transitions]. New transitions will automatically be started whenever [currentScene] is
- * changed.
- *
- * @param currentScene the current scene
- * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
- * This is called when the user commits a transition to a new scene because of a [UserAction], for
- * instance by triggering back navigation or by swiping to a new scene.
- * @param transitions the definition of the transitions used to animate a change of scene.
- * @param canChangeScene whether we can transition to the given scene. This is called when the user
- * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
- * `true`, then [onChangeScene] will be called right afterwards with the same [SceneKey]. If it
- * returns `false`, the user action will be cancelled and we will animate back to the current
- * scene.
- * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
- * [SceneTransitionLayoutState]s.
- */
-@Composable
-fun updateSceneTransitionLayoutState(
- currentScene: SceneKey,
- onChangeScene: (SceneKey) -> Unit,
- transitions: SceneTransitions = SceneTransitions.Empty,
- canChangeScene: (SceneKey) -> Boolean = { true },
- stateLinks: List<StateLink> = emptyList(),
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-): SceneTransitionLayoutState {
- return remember {
- HoistedSceneTransitionLayoutState(
- currentScene,
- transitions,
- onChangeScene,
- canChangeScene,
- stateLinks,
- enableInterruptions,
- )
- }
- .apply {
- update(
- currentScene,
- onChangeScene,
- canChangeScene,
- transitions,
- stateLinks,
- enableInterruptions,
- )
- }
-}
-
@Stable
sealed interface TransitionState {
/**
@@ -729,58 +674,6 @@
}
}
-/**
- * A [SceneTransitionLayout] whose current scene/source of truth is hoisted (its current value comes
- * from outside).
- */
-internal class HoistedSceneTransitionLayoutState(
- initialScene: SceneKey,
- override var transitions: SceneTransitions,
- private var changeScene: (SceneKey) -> Unit,
- private var canChangeScene: (SceneKey) -> Boolean,
- stateLinks: List<StateLink> = emptyList(),
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
-) : BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) {
- private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
-
- override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
-
- override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene.invoke(scene)
-
- @Composable
- fun update(
- currentScene: SceneKey,
- onChangeScene: (SceneKey) -> Unit,
- canChangeScene: (SceneKey) -> Boolean,
- transitions: SceneTransitions,
- stateLinks: List<StateLink>,
- enableInterruptions: Boolean,
- ) {
- SideEffect {
- this.changeScene = onChangeScene
- this.canChangeScene = canChangeScene
- this.transitions = transitions
- this.stateLinks = stateLinks
- this.enableInterruptions = enableInterruptions
-
- targetSceneChannel.trySend(currentScene)
- }
-
- LaunchedEffect(targetSceneChannel) {
- for (newKey in targetSceneChannel) {
- // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
- // late.
- val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
- animateToScene(
- layoutState = this@HoistedSceneTransitionLayoutState,
- target = newKey,
- transitionKey = null,
- )
- }
- }
- }
-}
-
/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 171e243..aeb6262 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -98,7 +98,9 @@
/** Whether swipe should be enabled in the given [orientation]. */
private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ return userActions.keys.any {
+ it is Swipe.Resolved && it.direction.orientation == orientation
+ }
}
private fun startDragImmediately(startedPosition: Offset): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index aa8dc38..7daefd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -44,26 +44,26 @@
return value
}
- return when (edge) {
- Edge.Top ->
+ return when (edge.resolve(layoutImpl.layoutDirection)) {
+ Edge.Resolved.Top ->
if (startsOutsideLayoutBounds) {
Offset(value.x, -elementSize.height.toFloat())
} else {
Offset(value.x, 0f)
}
- Edge.Left ->
+ Edge.Resolved.Left ->
if (startsOutsideLayoutBounds) {
Offset(-elementSize.width.toFloat(), value.y)
} else {
Offset(0f, value.y)
}
- Edge.Bottom ->
+ Edge.Resolved.Bottom ->
if (startsOutsideLayoutBounds) {
Offset(value.x, sceneSize.height.toFloat())
} else {
Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
}
- Edge.Right ->
+ Edge.Resolved.Right ->
if (startsOutsideLayoutBounds) {
Offset(sceneSize.width.toFloat(), value.y)
} else {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ff83d4b..7a5a84e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
@@ -61,8 +62,24 @@
canChangeScene = { canChangeScene(it) },
)
- val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
- val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ var layoutDirection = LayoutDirection.Rtl
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
@@ -94,6 +111,7 @@
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
+ layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
@@ -466,10 +484,8 @@
dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- mutableUserActionsA.remove(Swipe.Up)
- mutableUserActionsA.remove(Swipe.Down)
- mutableUserActionsB.remove(Swipe.Up)
- mutableUserActionsB.remove(Swipe.Down)
+ mutableUserActionsA = emptyMap()
+ mutableUserActionsB = emptyMap()
// start accelaratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
@@ -495,7 +511,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -512,7 +528,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
@@ -1149,8 +1165,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB)
+ mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
@@ -1178,8 +1193,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC)
+ mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
@@ -1220,7 +1234,8 @@
@Test
fun requireFullDistanceSwipe() = runGestureTest {
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true)
+ mutableUserActionsA +=
+ Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f)
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 1ae9992..7988e0e 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
@@ -203,26 +203,28 @@
val elementSize = 50.dp
val elementOffset = 20.dp
- lateinit var changeScene: (SceneKey) -> Unit
-
- rule.testTransition(
- from = SceneA,
- to = SceneB,
- transitionLayout = { currentScene, onChangeScene ->
- changeScene = onChangeScene
-
- SceneTransitionLayout(
- currentScene,
- onChangeScene,
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween }
from(SceneB, to = SceneC) { spec = tween }
},
- // Disable interruptions so that the current transition is directly removed when
- // starting a new one.
+ // Disable interruptions so that the current transition is directly removed
+ // when starting a new one.
enableInterruptions = false,
- ) {
+ )
+ }
+
+ lateinit var coroutineScope: CoroutineScope
+ rule.testTransition(
+ state = state,
+ to = SceneB,
+ transitionLayout = { state ->
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state) {
scene(SceneA) {
Box(Modifier.size(layoutSize)) {
// Transformed element
@@ -243,7 +245,7 @@
onElement(TestElements.Bar).assertExists()
// Start transition from SceneB to SceneC
- changeScene(SceneC)
+ rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
}
at(3 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
@@ -340,18 +342,16 @@
@Test
fun elementIsReusedBetweenScenes() {
- var currentScene by mutableStateOf(SceneA)
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
var sceneCState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+ lateinit var coroutineScope: CoroutineScope
rule.setContent {
+ coroutineScope = rememberCoroutineScope()
SceneTransitionLayoutForTesting(
- state =
- updateSceneTransitionLayoutState(
- currentScene = currentScene,
- onChangeScene = { currentScene = it }
- ),
+ state = state,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(SceneA) { /* Nothing */ }
@@ -375,7 +375,7 @@
assertThat(layoutImpl.elements).isEmpty()
// Scene B: element is in the map.
- currentScene = SceneB
+ rule.runOnUiThread { state.setTargetScene(SceneB, coroutineScope) }
rule.waitForIdle()
assertThat(layoutImpl.elements.keys).containsExactly(key)
@@ -383,7 +383,7 @@
assertThat(element.sceneStates.keys).containsExactly(SceneB)
// Scene C, state 0: the same element is reused.
- currentScene = SceneC
+ rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
sceneCState = 0
rule.waitForIdle()
@@ -472,12 +472,13 @@
@Test
fun elementModifierSupportsUpdates() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
var key by mutableStateOf(TestElements.Foo)
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
rule.setContent {
SceneTransitionLayoutForTesting(
- state = updateSceneTransitionLayoutState(currentScene = SceneA, onChangeScene = {}),
+ state = state,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(SceneA) { Box(Modifier.element(key)) }
@@ -521,11 +522,12 @@
rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
}
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
rule.setContent {
scrollScope = rememberCoroutineScope()
SceneTransitionLayoutForTesting(
- state = updateSceneTransitionLayoutState(currentScene = SceneA, onChangeScene = {}),
+ state = state,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 5543135..f717301 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -40,7 +40,13 @@
@Test
fun testObservableTransitionState() = runTest {
- lateinit var state: SceneTransitionLayoutState
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ EmptyTestTransitions,
+ )
+ }
// Collect the current observable state into [observableState].
// TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -63,16 +69,9 @@
}
rule.testTransition(
- from = SceneA,
+ state = state,
to = SceneB,
- transitionLayout = { currentScene, onChangeScene ->
- state =
- updateSceneTransitionLayoutState(
- currentScene,
- onChangeScene,
- EmptyTestTransitions
- )
-
+ transitionLayout = {
SceneTransitionLayout(state = state) {
scene(SceneA) {}
scene(SceneB) {}
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
new file mode 100644
index 0000000..6522eb3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.activity.BackEventCompat
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.subjects.assertThat
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PredictiveBackHandlerTest {
+ // We use createAndroidComposeRule() here and not createComposeRule() because we need an
+ // activity for testBack().
+ @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+
+ @Test
+ fun testBack() {
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ rule.setContent {
+ SceneTransitionLayout(layoutState) {
+ scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+ rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+ }
+
+ @Test
+ fun testPredictiveBack() {
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ rule.setContent {
+ SceneTransitionLayout(layoutState) {
+ scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+ // Start back.
+ val dispatcher = rule.activity.onBackPressedDispatcher
+ rule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(backEvent())
+ dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+ }
+
+ val transition = assertThat(layoutState.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ assertThat(transition).hasProgress(0.4f)
+
+ // Cancel it.
+ rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+ assertThat(layoutState.transitionState).isIdle()
+
+ // Start again and commit it.
+ rule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(backEvent())
+ dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+ dispatcher.onBackPressed()
+ }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+ assertThat(layoutState.transitionState).isIdle()
+ }
+
+ @Test
+ fun interruptedPredictiveBackDoesNotCallCanChangeScene() {
+ var canChangeSceneCalled = false
+ val layoutState =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ canChangeScene = {
+ canChangeSceneCalled = true
+ true
+ },
+ )
+ }
+
+ lateinit var coroutineScope: CoroutineScope
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(layoutState) {
+ scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+ // Start back.
+ val dispatcher = rule.activity.onBackPressedDispatcher
+ rule.runOnUiThread { dispatcher.dispatchOnBackStarted(backEvent()) }
+
+ val predictiveTransition = assertThat(layoutState.transitionState).isTransition()
+ assertThat(predictiveTransition).hasFromScene(SceneA)
+ assertThat(predictiveTransition).hasToScene(SceneB)
+
+ // Start a new transition to C.
+ rule.runOnUiThread { layoutState.setTargetScene(SceneC, coroutineScope) }
+ val newTransition = assertThat(layoutState.transitionState).isTransition()
+ assertThat(newTransition).hasFromScene(SceneA)
+ assertThat(newTransition).hasToScene(SceneC)
+
+ // Commit the back gesture. It shouldn't call canChangeScene given that the back transition
+ // was interrupted.
+ rule.runOnUiThread { dispatcher.onBackPressed() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(predictiveTransition).hasCurrentScene(SceneA)
+ assertThat(canChangeSceneCalled).isFalse()
+ }
+
+ private fun backEvent(progress: Float = 0f): BackEventCompat {
+ return BackEventCompat(
+ touchX = 0f,
+ touchY = 0f,
+ progress = progress,
+ swipeEdge = BackEventCompat.EDGE_LEFT,
+ )
+ }
+}
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 1c8efb8..1ec1079 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
@@ -16,8 +16,6 @@
package com.android.compose.animation.scene
-import androidx.activity.BackEventCompat
-import androidx.activity.ComponentActivity
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
@@ -31,6 +29,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -41,7 +41,7 @@
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onChild
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@@ -58,6 +58,7 @@
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -69,23 +70,27 @@
private val LayoutSize = 300.dp
}
- private var currentScene by mutableStateOf(SceneA)
- private lateinit var layoutState: SceneTransitionLayoutState
+ private lateinit var coroutineScope: CoroutineScope
+ private lateinit var layoutState: MutableSceneTransitionLayoutState
+ private var currentScene: SceneKey
+ get() = layoutState.transitionState.currentScene
+ set(value) {
+ rule.runOnUiThread { layoutState.setTargetScene(value, coroutineScope) }
+ }
- // We use createAndroidComposeRule() here and not createComposeRule() because we need an
- // activity for testBack().
- @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+ @get:Rule val rule = createComposeRule()
/** The content under test. */
@Composable
private fun TestContent(enableInterruptions: Boolean = true) {
- layoutState =
- updateSceneTransitionLayoutState(
- currentScene,
- { currentScene = it },
+ coroutineScope = rememberCoroutineScope()
+ layoutState = remember {
+ MutableSceneTransitionLayoutState(
+ SceneA,
EmptyTestTransitions,
enableInterruptions = enableInterruptions,
)
+ }
SceneTransitionLayout(
state = layoutState,
@@ -164,52 +169,6 @@
}
@Test
- fun testBack() {
- rule.setContent { TestContent() }
-
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
- rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
- rule.waitForIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
- }
-
- @Test
- fun testPredictiveBack() {
- rule.setContent { TestContent() }
-
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
- // Start back.
- val dispatcher = rule.activity.onBackPressedDispatcher
- rule.runOnUiThread {
- dispatcher.dispatchOnBackStarted(backEvent())
- dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
- }
-
- val transition = assertThat(layoutState.transitionState).isTransition()
- assertThat(transition).hasFromScene(SceneA)
- assertThat(transition).hasToScene(SceneB)
- assertThat(transition).hasProgress(0.4f)
-
- // Cancel it.
- rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
- rule.waitForIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
- assertThat(layoutState.transitionState).isIdle()
-
- // Start again and commit it.
- rule.runOnUiThread {
- dispatcher.dispatchOnBackStarted(backEvent())
- dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
- dispatcher.onBackPressed()
- }
- rule.waitForIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
- assertThat(layoutState.transitionState).isIdle()
- }
-
- @Test
fun testTransitionState() {
rule.setContent { TestContent() }
assertThat(layoutState.transitionState).isIdle()
@@ -218,23 +177,15 @@
// We will advance the clock manually.
rule.mainClock.autoAdvance = false
- // Change the current scene. Until composition is triggered, this won't change the layout
- // state.
+ // Change the current scene.
currentScene = SceneB
- assertThat(layoutState.transitionState).isIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
-
- // On the next frame, we will recompose because currentScene changed, which will start the
- // transition (i.e. it will change the transitionState to be a Transition) in a
- // LaunchedEffect.
- rule.mainClock.advanceTimeByFrame()
val transition = assertThat(layoutState.transitionState).isTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
assertThat(transition).hasProgress(0f)
// Then, on the next frame, the animator we started gets its initial value and clock
- // starting time. We are now at progress = 0f.
+ // starting time. We are still at progress = 0f.
rule.mainClock.advanceTimeByFrame()
assertThat(transition).hasProgress(0f)
@@ -275,12 +226,9 @@
// Pause animations to test the state mid-transition.
rule.mainClock.autoAdvance = false
- // Go to scene B and let the animation start. See [testLayoutState()] and
- // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
- // by 2 frames to be at the start of the animation.
+ // Go to scene B and let the animation start.
currentScene = SceneB
rule.mainClock.advanceTimeByFrame()
- rule.mainClock.advanceTimeByFrame()
// Advance to the middle of the animation.
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -311,7 +259,6 @@
// Animate to scene C, let the animation start then go to the middle of the transition.
currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
- rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
// In Scene C, foo is at the bottom start of the layout and has a size of 150.dp. The
@@ -409,24 +356,24 @@
fun multipleTransitionsWillComposeMultipleScenes() {
val duration = 10 * 16L
- var currentScene: SceneKey by mutableStateOf(SceneA)
- lateinit var state: SceneTransitionLayoutState
- rule.setContent {
- state =
- updateSceneTransitionLayoutState(
- currentScene = currentScene,
- onChangeScene = { currentScene = it },
- transitions =
- transitions {
- from(SceneA, to = SceneB) {
- spec = tween(duration.toInt(), easing = LinearEasing)
- }
- from(SceneB, to = SceneC) {
- spec = tween(duration.toInt(), easing = LinearEasing)
- }
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions {
+ from(SceneA, to = SceneB) {
+ spec = tween(duration.toInt(), easing = LinearEasing)
}
+ from(SceneB, to = SceneC) {
+ spec = tween(duration.toInt(), easing = LinearEasing)
+ }
+ }
)
+ }
+ lateinit var coroutineScope: CoroutineScope
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
SceneTransitionLayout(state) {
scene(SceneA) { Box(Modifier.testTag("aRoot").fillMaxSize()) }
scene(SceneB) { Box(Modifier.testTag("bRoot").fillMaxSize()) }
@@ -444,12 +391,11 @@
rule.mainClock.autoAdvance = false
// Start A => B and go to the middle of the transition.
- currentScene = SceneB
+ rule.runOnUiThread { state.setTargetScene(SceneB, coroutineScope) }
- // We need to tick 2 frames after changing [currentScene] before the animation actually
+ // We need to tick 1 frames after changing [currentScene] before the animation actually
// starts.
rule.mainClock.advanceTimeByFrame()
- rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(duration / 2)
rule.waitForIdle()
@@ -462,8 +408,7 @@
rule.onNodeWithTag("cRoot").assertDoesNotExist()
// Start B => C.
- currentScene = SceneC
- rule.mainClock.advanceTimeByFrame()
+ rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
rule.mainClock.advanceTimeByFrame()
rule.waitForIdle()
@@ -517,12 +462,7 @@
assertThrows(IllegalStateException::class.java) {
rule.setContent {
SceneTransitionLayout(
- state =
- updateSceneTransitionLayoutState(
- currentScene = currentScene,
- onChangeScene = { currentScene = it },
- transitions = EmptyTestTransitions
- ),
+ state = remember { MutableSceneTransitionLayoutState(SceneA) },
modifier = Modifier.size(LayoutSize),
) {
// from SceneA to SceneA
@@ -560,13 +500,4 @@
assertThat(keyInB).isEqualTo(SceneB)
assertThat(keyInC).isEqualTo(SceneC)
}
-
- private fun backEvent(progress: Float = 0f): BackEventCompat {
- return BackEventCompat(
- touchX = 0f,
- touchY = 0f,
- progress = progress,
- swipeEdge = BackEventCompat.EDGE_LEFT,
- )
- }
}
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 25ea2ee..0766e00 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
@@ -23,11 +23,13 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -37,10 +39,12 @@
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -634,4 +638,152 @@
// Foo should be translated by (20dp, 30dp).
rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
}
+
+ @Test
+ fun startEnd_ltrLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (left in LTR), so we make
+ // scene B appear from the end (right) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (right in LTR), so we make
+ // scene C appear from the start (left) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+
+ // Swipe to the left (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene B should come from the right (end) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (start) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
+
+ @Test
+ fun startEnd_rtlLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (right in RTL), so we make
+ // scene B appear from the end (left) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (left in RTL), so we make
+ // scene C appear from the start (right) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+ }
+
+ // Swipe to the left (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the right (start) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (end) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
index de46f72..fbd557f 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
@@ -27,12 +27,6 @@
content: @Composable SceneScope.() -> Unit,
) {
val currentScene = remember { SceneKey("current") }
- SceneTransitionLayout(
- currentScene,
- onChangeScene = { /* do nothing */},
- transitions = remember { transitions {} },
- modifier,
- ) {
- scene(currentScene, content = content)
- }
+ val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+ SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 6724851..a37d78e 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
@@ -19,13 +19,14 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import kotlinx.coroutines.CoroutineScope
import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion
import platform.test.motion.compose.ComposeRecordingSpec
@@ -95,20 +96,24 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- from = fromScene,
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) }
+ )
+ },
to = toScene,
- transitionLayout = { currentScene, onChangeScene ->
+ transitionLayout = { state ->
SceneTransitionLayout(
- currentScene,
- onChangeScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
+ state,
layoutModifier,
) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
}
},
- builder,
+ builder = builder,
)
}
@@ -172,21 +177,19 @@
)
}
-/**
- * Test the transition between two scenes of [transitionLayout][SceneTransitionLayout] at different
- * points in time.
- */
+/** Test the transition from [state] to [to]. */
fun ComposeContentTestRule.testTransition(
- from: SceneKey,
+ state: MutableSceneTransitionLayoutState,
to: SceneKey,
- transitionLayout:
- @Composable
- (
- currentScene: SceneKey,
- onChangeScene: (SceneKey) -> Unit,
- ) -> Unit,
+ transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
builder: TransitionTestBuilder.() -> Unit,
) {
+ val currentScene = state.transitionState.currentScene
+ check(currentScene != to) {
+ "The 'to' scene (${to.debugName}) should be different from the state current scene " +
+ "(${currentScene.debugName})"
+ }
+
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
@@ -198,8 +201,11 @@
}
}
- var currentScene by mutableStateOf(from)
- setContent { transitionLayout(currentScene, { currentScene = it }) }
+ lateinit var coroutineScope: CoroutineScope
+ setContent {
+ coroutineScope = rememberCoroutineScope()
+ transitionLayout(state)
+ }
// Wait for the UI to be idle then test the before state.
waitForIdle()
@@ -209,14 +215,8 @@
mainClock.autoAdvance = false
// Change the current scene.
- currentScene = to
-
- // Advance by a frame to trigger recomposition, which will start the transition (i.e. it will
- // change the transitionState to be a Transition) in a LaunchedEffect.
- mainClock.advanceTimeByFrame()
-
- // Advance by another frame so that the animator we started gets its initial value and clock
- // starting time. We are now at progress = 0f.
+ runOnUiThread { state.setTargetScene(to, coroutineScope) }
+ waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 51c008a..9b725eb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -17,7 +17,6 @@
import android.app.UserSwitchObserver
import android.content.Context
import android.database.ContentObserver
-import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
@@ -33,6 +32,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -341,6 +341,7 @@
}
private var isClockChanged = AtomicBoolean(false)
+
private fun triggerOnCurrentClockChanged() {
val shouldSchedule = isClockChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -355,6 +356,7 @@
}
private var isClockListChanged = AtomicBoolean(false)
+
private fun triggerOnAvailableClocksChanged() {
val shouldSchedule = isClockListChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -458,6 +460,7 @@
}
private var isQueued = AtomicBoolean(false)
+
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -565,8 +568,8 @@
return availableClocks.map { (_, clock) -> clock.metadata }
}
- fun getClockThumbnail(clockId: ClockId): Drawable? =
- availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+ fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
+ availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
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 9e0af97..4802e34 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
@@ -15,13 +15,13 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -60,12 +60,17 @@
)
}
- override fun getClockThumbnail(id: ClockId): Drawable? {
+ override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- // TODO(b/352049256): Update placeholder to actual resource
- return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ return ClockPickerConfig(
+ DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ // TODO(b/352049256): Update placeholder to actual resource
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ )
}
}
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 525839d..b4c839f 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -9168,39 +9168,6 @@
<issue
id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2654"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleShow");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2780"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="3011"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
message="The `traceBegin()` call is not always closed with a matching `traceEnd()` because the code in between may return early"
errorLine1=" Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");"
errorLine2=" ~~~~~~~~~~">
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index f9f7df8..418cecd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -36,7 +36,7 @@
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -83,7 +83,7 @@
underTest = kosmos.bouncerMessageViewModel
overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
"not mainline reboot"
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
index 722eb2b..60aea92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
@@ -20,6 +20,9 @@
import android.content.Context
import android.content.Intent
import android.content.mockedContext
+import android.os.Handler
+import android.os.fakeExecutorHandler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,11 +30,13 @@
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.widgets.CommunalWidgetModule
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,6 +46,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,10 +57,13 @@
@Mock private lateinit var communalInteractor: CommunalInteractor
- private val mapCaptor = kotlinArgumentCaptor<Map<Int, Int>>()
+ private val mapCaptor = argumentCaptor<Map<Int, Int>>()
private lateinit var context: Context
private lateinit var broadcastDispatcher: FakeBroadcastDispatcher
+ private lateinit var secureSettings: SecureSettings
+ private lateinit var handler: Handler
+ private lateinit var fakeExecutor: FakeExecutor
private lateinit var underTest: CommunalBackupRestoreStartable
@Before
@@ -62,18 +72,28 @@
context = kosmos.mockedContext
broadcastDispatcher = kosmos.broadcastDispatcher
+ secureSettings = kosmos.fakeSettings
+ handler = kosmos.fakeExecutorHandler
+ fakeExecutor = kosmos.fakeExecutor
+
+ secureSettings.putInt(USER_SETUP_COMPLETE, 0)
underTest =
CommunalBackupRestoreStartable(
broadcastDispatcher,
communalInteractor,
logcatLogBuffer("CommunalBackupRestoreStartable"),
+ secureSettings,
+ handler,
)
}
@Test
- fun testRestoreWidgetsUponHostRestored() =
+ fun restoreWidgets_userSetUpComplete_performRestore() =
testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
underTest.start()
// Verify restore widgets not called
@@ -94,7 +114,7 @@
// Verify restore widgets called
verify(communalInteractor).restoreWidgets(mapCaptor.capture())
- val oldToNewWidgetIdMap = mapCaptor.value
+ val oldToNewWidgetIdMap = mapCaptor.firstValue
assertThat(oldToNewWidgetIdMap)
.containsExactlyEntriesIn(
mapOf(
@@ -106,10 +126,54 @@
}
@Test
- fun testDoNotRestoreWidgetsIfNotForCommunalWidgetHost() =
+ fun restoreWidgets_userSetUpNotComplete_restoreWhenUserSetupComplete() =
testScope.runTest {
underTest.start()
+ // Verify restore widgets not called
+ verify(communalInteractor, never()).restoreWidgets(any())
+
+ // Trigger app widget host restored
+ val intent =
+ Intent().apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED
+ putExtra(
+ AppWidgetManager.EXTRA_HOST_ID,
+ CommunalWidgetModule.APP_WIDGET_HOST_ID
+ )
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, intArrayOf(1, 2, 3))
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(7, 8, 9))
+ }
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ // Verify restore widgets not called because user setup not complete
+ verify(communalInteractor, never()).restoreWidgets(any())
+
+ // User setup complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+ fakeExecutor.runAllReady()
+
+ // Verify restore widgets called
+ verify(communalInteractor).restoreWidgets(mapCaptor.capture())
+ val oldToNewWidgetIdMap = mapCaptor.firstValue
+ assertThat(oldToNewWidgetIdMap)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, 7),
+ Pair(2, 8),
+ Pair(3, 9),
+ )
+ )
+ }
+
+ @Test
+ fun restoreWidgets_broadcastNotForCommunalWidgetHost_doNotPerformRestore() =
+ testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
+ underTest.start()
+
// Trigger app widget host restored, but for another host
val hostId = CommunalWidgetModule.APP_WIDGET_HOST_ID + 1
val intent =
@@ -126,8 +190,11 @@
}
@Test
- fun testAbortRestoreWidgetsIfOldToNewIdsMappingInvalid() =
+ fun restoreWidgets_oldToNewIdsMappingInvalid_abortRestore() =
testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
underTest.start()
// Trigger app widget host restored, but new ids list is one too many for old ids
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
new file mode 100644
index 0000000..2f94933
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.communal
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnableFlags(FLAG_COMMUNAL_HUB)
+@RunWith(AndroidJUnit4::class)
+class CommunalOngoingContentStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mediaRepository = kosmos.fakeCommunalMediaRepository
+ private val smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
+ private val featureFlags =
+ kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+
+ private lateinit var underTest: CommunalOngoingContentStartable
+
+ @Before
+ fun setUp() {
+ underTest =
+ CommunalOngoingContentStartable(
+ bgScope = kosmos.applicationCoroutineScope,
+ communalInteractor = kosmos.communalInteractor,
+ communalMediaRepository = mediaRepository,
+ communalSmartspaceRepository = smartspaceRepository,
+ featureFlags = featureFlags,
+ )
+ }
+
+ @Test
+ fun testListenForOngoingContentWhenCommunalIsEnabled() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ assertThat(mediaRepository.isListening()).isFalse()
+ assertThat(smartspaceRepository.isListening()).isFalse()
+
+ kosmos.setCommunalEnabled(true)
+ runCurrent()
+
+ assertThat(mediaRepository.isListening()).isTrue()
+ assertThat(smartspaceRepository.isListening()).isTrue()
+
+ kosmos.setCommunalEnabled(false)
+ runCurrent()
+
+ assertThat(mediaRepository.isListening()).isFalse()
+ assertThat(smartspaceRepository.isListening()).isFalse()
+ }
+}
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 cf14547..2cbfa7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.communal
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
@@ -27,6 +29,9 @@
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.fakeFeatureFlagsClassic
+import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,6 +62,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB)
class CommunalSceneStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -66,6 +72,7 @@
fun setUp() {
with(kosmos) {
fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
underTest =
CommunalSceneStartable(
@@ -76,6 +83,7 @@
keyguardInteractor = keyguardInteractor,
systemSettings = fakeSettings,
notificationShadeWindowController = notificationShadeWindowController,
+ featureFlagsClassic = kosmos.fakeFeatureFlagsClassic,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
@@ -93,7 +101,7 @@
}
@Test
- fun keyguardGoesAway_forceBlankScene() =
+ fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
@@ -101,6 +109,27 @@
communalSceneInteractor.changeScene(CommunalScenes.Communal)
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
+ fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalSceneInteractor.setIsLaunchingWidget(false)
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
@@ -451,6 +480,24 @@
}
}
+ @Test
+ fun transitionFromDozingToGlanceableHub_forcesCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ 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()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
new file mode 100644
index 0000000..ad2c42f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.communal.data.db
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
+import com.android.systemui.communal.widgets.CommunalWidgetHost
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class DefaultWidgetPopulationTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val communalWidgetHost =
+ mock<CommunalWidgetHost> {
+ var nextId = 0
+ on { allocateIdAndBindWidget(any(), anyOrNull()) }.thenAnswer { nextId++ }
+ }
+ private val communalWidgetDao = mock<CommunalWidgetDao>()
+ private val database = mock<SupportSQLiteDatabase>()
+ private val mainUser = UserHandle(0)
+ private val userManager =
+ mock<UserManager> {
+ on { mainUser }.thenReturn(mainUser)
+ on { getUserSerialNumber(0) }.thenReturn(0)
+ }
+
+ private val defaultWidgets =
+ arrayOf(
+ "com.android.test_package_1/fake_widget_1",
+ "com.android.test_package_2/fake_widget_2",
+ "com.android.test_package_3/fake_widget_3",
+ )
+
+ private lateinit var underTest: DefaultWidgetPopulation
+
+ @Before
+ fun setUp() {
+ underTest =
+ DefaultWidgetPopulation(
+ bgScope = kosmos.applicationCoroutineScope,
+ communalWidgetHost = communalWidgetHost,
+ communalWidgetDaoProvider = { communalWidgetDao },
+ defaultWidgets = defaultWidgets,
+ logBuffer = logcatLogBuffer("DefaultWidgetPopulationTest"),
+ userManager = userManager,
+ )
+ }
+
+ @Test
+ fun testPopulateDefaultWidgetsWhenDatabaseCreated() =
+ testScope.runTest {
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify default widgets bound
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[0])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[1])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[2])!!),
+ user = eq(mainUser),
+ )
+
+ // Verify default widgets added in database
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 0,
+ componentName = defaultWidgets[0],
+ priority = 3,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 1,
+ componentName = defaultWidgets[1],
+ priority = 2,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 2,
+ componentName = defaultWidgets[2],
+ priority = 1,
+ userSerialNumber = 0,
+ )
+ }
+
+ @Test
+ fun testSkipDefaultWidgetsPopulation() =
+ testScope.runTest {
+ // Skip default widgets population
+ underTest.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify no widget bounded or added to the database
+ verify(communalWidgetHost, never()).allocateIdAndBindWidget(any(), any())
+ verify(communalWidgetDao, never())
+ .addWidget(
+ widgetId = anyInt(),
+ componentName = any(),
+ priority = anyInt(),
+ userSerialNumber = anyInt(),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 407bf4c..dd28022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,46 +20,41 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
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.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var mediaDataManager: MediaDataManager
- @Mock private lateinit var mediaData: MediaData
- @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ private val mediaDataManager = mock<MediaDataManager>()
+ private val mediaData = mock<MediaData>()
+ private val tableLogBuffer = mock<TableLogBuffer>()
private lateinit var underTest: CommunalMediaRepositoryImpl
- private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
- KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
- }
+ private val mediaDataListenerCaptor = argumentCaptor<MediaDataManager.Listener>()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
-
underTest =
CommunalMediaRepositoryImpl(
mediaDataManager,
@@ -78,6 +73,8 @@
@Test
fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
+ underTest.startListening()
+
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -89,7 +86,7 @@
// Change to media available and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
- mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+ mediaDataListenerCaptor.firstValue.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
@@ -100,12 +97,14 @@
@Test
fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
+ underTest.startListening()
+
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Change to media available and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
- mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+ mediaDataListenerCaptor.firstValue.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
@@ -114,7 +113,7 @@
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
+ mediaDataListenerCaptor.firstValue.onMediaDataRemoved("key", false)
runCurrent()
// Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
new file mode 100644
index 0000000..c1816ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
@@ -0,0 +1,319 @@
+/*
+ * 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.communal.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import android.app.smartspace.flags.Flags.FLAG_REMOTE_VIEWS
+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.systemui.SysuiTestCase
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalSmartspaceRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val listenerCaptor = argumentCaptor<SmartspaceTargetListener>()
+
+ private val smartspaceController = mock<CommunalSmartspaceController>()
+ private val fakeExecutor = kosmos.fakeExecutor
+ private val systemClock = kosmos.fakeSystemClock
+
+ private lateinit var underTest: CommunalSmartspaceRepositoryImpl
+
+ @Before
+ fun setUp() {
+ underTest =
+ CommunalSmartspaceRepositoryImpl(
+ smartspaceController,
+ fakeExecutor,
+ systemClock,
+ )
+ }
+
+ @DisableFlags(FLAG_REMOTE_VIEWS)
+ @Test
+ fun startListening_remoteViewsFlagDisabled_doNotListenForSmartspaceUpdates() =
+ testScope.runTest {
+ underTest.startListening()
+ fakeExecutor.runAllReady()
+
+ verify(smartspaceController, never()).addListener(any())
+ }
+
+ @EnableFlags(FLAG_REMOTE_VIEWS)
+ @Test
+ fun startListening_remoteViewsFlagEnabled_listenForSmartspaceUpdates() =
+ testScope.runTest {
+ underTest.startListening()
+ fakeExecutor.runAllReady()
+
+ // Verify listener added
+ val listener = captureSmartspaceTargetListener()
+
+ underTest.stopListening()
+ fakeExecutor.runAllReady()
+
+ // Verify listener removed
+ verify(smartspaceController).removeListener(listener)
+ }
+
+ @EnableFlags(FLAG_REMOTE_VIEWS)
+ @Test
+ fun communalTimers_onlyShowTimersWithRemoteViews() =
+ testScope.runTest {
+ underTest.startListening()
+
+ val communalTimers by collectLastValue(underTest.timers)
+ runCurrent()
+ fakeExecutor.runAllReady()
+
+ with(captureSmartspaceTargetListener()) {
+ onSmartspaceTargetsUpdated(
+ listOf(
+ // Invalid. Not a timer
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("weather")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_WEATHER)
+ },
+ // Invalid. RemoteViews absent
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(null)
+ on { creationTimeMillis }.doReturn(1000)
+ },
+ // Valid
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-1-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(2000)
+ },
+ )
+ )
+ }
+ runCurrent()
+
+ // Verify that only the valid target is listed
+ assertThat(communalTimers).hasSize(1)
+ assertThat(communalTimers?.first()?.smartspaceTargetId).isEqualTo("timer-1-started")
+ }
+
+ @EnableFlags(FLAG_REMOTE_VIEWS)
+ @Test
+ fun communalTimers_cacheCreationTime() =
+ testScope.runTest {
+ underTest.startListening()
+
+ val communalTimers by collectLastValue(underTest.timers)
+ runCurrent()
+ fakeExecutor.runAllReady()
+
+ val listener = captureSmartspaceTargetListener()
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(1000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify that the creation time is the current time, not the creation time passed in
+ // the target, because this value can be inaccurate (due to b/318535930).
+ val currentTime = systemClock.currentTimeMillis()
+ assertThat(communalTimers?.get(0)?.createdTimestampMillis).isEqualTo(currentTime)
+ assertThat(communalTimers?.get(0)?.createdTimestampMillis).isNotEqualTo(1000)
+
+ // A second timer is added.
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(2000)
+ },
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-1-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(3000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify that the created timestamp for the first time is consistent
+ assertThat(communalTimers?.get(0)?.createdTimestampMillis).isEqualTo(currentTime)
+
+ // Verify that the second timer has a new creation time
+ assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+ .isEqualTo(systemClock.currentTimeMillis())
+ }
+
+ @EnableFlags(FLAG_REMOTE_VIEWS)
+ @Test
+ fun communalTimers_creationTimeRemovedFromCacheWhenTimerRemoved() =
+ testScope.runTest {
+ underTest.startListening()
+
+ val communalTimers by collectLastValue(underTest.timers)
+ runCurrent()
+ fakeExecutor.runAllReady()
+
+ // Start timer 0
+ val listener = captureSmartspaceTargetListener()
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(1000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify timer 0 creation time
+ val expectedCreationTimeForTimer0 = systemClock.currentTimeMillis()
+ assertThat(communalTimers?.first()?.createdTimestampMillis)
+ .isEqualTo(expectedCreationTimeForTimer0)
+
+ // Advance some time
+ systemClock.advanceTime(1000)
+
+ // Start timer 1
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(1000)
+ },
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-1-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(2000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify timer 1 creation time is new
+ val expectedCreationTimeForTimer1 = expectedCreationTimeForTimer0 + 1000
+ assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+ .isEqualTo(expectedCreationTimeForTimer1)
+
+ // Removed timer 0
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-1-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(2000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify timer 0 removed, and timer 1 creation time is correct
+ assertThat(communalTimers).hasSize(1)
+ assertThat(communalTimers?.first()?.createdTimestampMillis)
+ .isEqualTo(expectedCreationTimeForTimer1)
+
+ // Advance some time
+ systemClock.advanceTime(1000)
+
+ // Start timer 0 again. Technically this is a new timer, but timers can reused stable
+ // ids.
+ listener.onSmartspaceTargetsUpdated(
+ listOf(
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-1-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(2000)
+ },
+ mock<SmartspaceTarget> {
+ on { smartspaceTargetId }.doReturn("timer-0-started")
+ on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER)
+ on { remoteViews }.doReturn(mock())
+ on { creationTimeMillis }.doReturn(3000)
+ },
+ )
+ )
+ runCurrent()
+
+ // Verify new timer added, and timer 1 creation time is still correct
+ assertThat(communalTimers).hasSize(2)
+ assertThat(communalTimers?.get(0)?.createdTimestampMillis)
+ .isEqualTo(expectedCreationTimeForTimer1)
+
+ // Verify creation time for the new timer is new, meaning that cache for timer 0 was
+ // removed when it was removed
+ assertThat(communalTimers?.get(1)?.createdTimestampMillis)
+ .isEqualTo(expectedCreationTimeForTimer1 + 1000)
+ }
+
+ @Test
+ fun stableId() {
+ assertThat(CommunalSmartspaceRepositoryImpl.stableId("timer-0-12345-started"))
+ .isEqualTo("timer-0")
+ assertThat(CommunalSmartspaceRepositoryImpl.stableId("timer-1-67890-paused"))
+ .isEqualTo("timer-1")
+ assertThat(CommunalSmartspaceRepositoryImpl.stableId("i_am_an_unexpected_id"))
+ .isEqualTo("i_am_an_unexpected_id")
+ }
+
+ private fun captureSmartspaceTargetListener(): SmartspaceTargetListener {
+ verify(smartspaceController).addListener(listenerCaptor.capture())
+ return listenerCaptor.firstValue
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 6ce6cdb..c707ebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -24,6 +24,7 @@
import android.content.applicationContext
import android.graphics.Bitmap
import android.os.UserHandle
+import android.os.userManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,6 +34,7 @@
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.defaultWidgetPopulation
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toByteArray
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -47,10 +49,6 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,11 +57,16 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.eq
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.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -77,6 +80,9 @@
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
@Mock private lateinit var backupManager: BackupManager
+ private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
+ private val componentNameCaptor = argumentCaptor<ComponentName>()
+
private lateinit var backupUtils: CommunalBackupUtils
private lateinit var logBuffer: LogBuffer
private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
@@ -85,6 +91,10 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val packageChangeRepository = kosmos.fakePackageChangeRepository
+ private val userManager = kosmos.userManager
+
+ private val mainUser = UserHandle(0)
+ private val workProfile = UserHandle(10)
private val fakeAllowlist =
listOf(
@@ -109,6 +119,9 @@
whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
+ whenever(userManager.mainUser).thenReturn(mainUser)
+
+ restoreUser(mainUser)
underTest =
CommunalWidgetRepositoryImpl(
@@ -121,6 +134,8 @@
backupManager,
backupUtils,
packageChangeRepository,
+ userManager,
+ kosmos.defaultWidgetPopulation,
)
}
@@ -128,7 +143,7 @@
fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
- val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
+ val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0)
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -154,13 +169,13 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
CommunalItemRank(uid = 3L, rank = 3) to
- CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L),
+ CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0),
CommunalItemRank(uid = 4L, rank = 4) to
- CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L),
+ CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0),
)
fakeProviders.value =
mapOf(
@@ -192,9 +207,9 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
)
fakeProviders.value =
mapOf(
@@ -249,7 +264,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -259,11 +273,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao)
+ .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -275,7 +290,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -285,11 +299,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao, never())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -302,7 +317,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -312,13 +326,14 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority) {
+ underTest.addWidget(provider, mainUser, priority) {
throw IllegalStateException("some error")
}
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao, never())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -331,7 +346,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
whenever(
@@ -341,11 +355,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao)
+ .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -444,11 +459,8 @@
runCurrent()
// Verify state restored, and widget 2 skipped
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(1)
val restoredWidget = restoredWidgets.first()
@@ -474,11 +486,8 @@
runCurrent()
// Verify widget 1 and 2 are restored, and are now 11 and 12.
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(2)
val restoredWidget1 = restoredWidgets[0]
@@ -512,11 +521,8 @@
runCurrent()
// Verify widget 1 and 2 are restored, and are now 1 and 12.
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(2)
val restoredWidget1 = restoredWidgets[0]
@@ -533,14 +539,134 @@
}
@Test
+ fun restoreWidgets_undefinedUser_restoredAsMain() =
+ testScope.runTest {
+ // Write two widgets to file, both of which have user serial number undefined.
+ val fakeState =
+ CommunalHubState().apply {
+ widgets =
+ listOf(
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 1
+ componentName = "pk_name/fake_widget_1"
+ rank = 1
+ userSerialNumber =
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+ },
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 2
+ componentName = "pk_name/fake_widget_2"
+ rank = 2
+ userSerialNumber =
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+ },
+ )
+ .toTypedArray()
+ }
+ backupUtils.writeBytesToDisk(fakeState.toByteArray())
+
+ // Set up app widget host with widget ids.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify widget 1 and 2 are restored with the main user.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(2)
+
+ val restoredWidget1 = restoredWidgets[0]
+ assertThat(restoredWidget1.widgetId).isEqualTo(11)
+ assertThat(restoredWidget1.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+ val restoredWidget2 = restoredWidgets[1]
+ assertThat(restoredWidget2.widgetId).isEqualTo(12)
+ assertThat(restoredWidget2.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+ }
+
+ @Test
+ fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
+ testScope.runTest {
+ // Write fake state to file
+ backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+ // Set up app widget host with widget ids.
+ // (b/349852237) It's possible that the platform restores widgets even though their user
+ // is not restored.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify only widget 1 is restored. Widget 2 is skipped because it belongs to a work
+ // profile, which is not restored.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(1)
+
+ val restoredWidget = restoredWidgets[0]
+ assertThat(restoredWidget.widgetId).isEqualTo(11)
+ assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+ }
+
+ @Test
+ fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
+ testScope.runTest {
+ // Write fake state to file
+ backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+ // Set up app widget host with widget ids.
+ // (b/349852237) It's possible that the platform restores widgets even though their user
+ // is not restored.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore work profile.
+ restoreUser(workProfile)
+
+ val newWidgetId = 13
+ whenever(communalWidgetHost.allocateIdAndBindWidget(any(), any()))
+ .thenReturn(newWidgetId)
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify widget 1 is restored.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(1)
+
+ val restoredWidget = restoredWidgets[0]
+ assertThat(restoredWidget.widgetId).isEqualTo(11)
+ assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+ // Verify widget 2 (now 12) is removed from platform
+ verify(appWidgetHost).deleteAppWidgetId(12)
+
+ // Verify work profile widget is manually bound
+ verify(communalWidgetDao)
+ .addWidget(
+ eq(newWidgetId),
+ componentNameCaptor.capture(),
+ eq(2),
+ eq(testUserSerialNumber(workProfile))
+ )
+ assertThat(componentNameCaptor.firstValue)
+ .isEqualTo(ComponentName("pk_name", "fake_widget_2"))
+ }
+
+ @Test
fun pendingWidgets() =
testScope.runTest {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
)
// Widget 1 is installed
@@ -554,7 +680,7 @@
sessionId = 1,
packageName = "pk_2",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
)
)
)
@@ -572,7 +698,7 @@
priority = 2,
packageName = "pk_2",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
),
)
}
@@ -583,7 +709,7 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
)
// Widget 1 is pending install
@@ -594,7 +720,7 @@
sessionId = 1,
packageName = "pk_1",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
)
)
)
@@ -607,7 +733,7 @@
priority = 1,
packageName = "pk_1",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
),
)
@@ -633,6 +759,20 @@
whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
+ // Commonly the user id and user serial number are the same, but for testing purposes use a
+ // simple algorithm to map a user id to a different user serial number to make sure the correct
+ // value is used.
+ private fun testUserSerialNumber(user: UserHandle): Int {
+ return user.identifier + 100
+ }
+
+ private fun restoreUser(user: UserHandle) {
+ whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
+ .thenReturn(user)
+ whenever(userManager.getUserSerialNumber(user.identifier))
+ .thenReturn(testUserSerialNumber(user))
+ }
+
private companion object {
val PROVIDER_INFO_REQUIRES_CONFIGURATION =
AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
@@ -650,11 +790,32 @@
widgetId = 1
componentName = "pk_name/fake_widget_1"
rank = 1
+ userSerialNumber = 0
},
CommunalHubState.CommunalWidgetItem().apply {
widgetId = 2
componentName = "pk_name/fake_widget_2"
rank = 2
+ userSerialNumber = 0
+ },
+ )
+ .toTypedArray()
+ }
+ val fakeStateWithWorkProfile =
+ CommunalHubState().apply {
+ widgets =
+ listOf(
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 1
+ componentName = "pk_name/fake_widget_1"
+ rank = 1
+ userSerialNumber = 0
+ },
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 2
+ componentName = "pk_name/fake_widget_2"
+ rank = 2
+ userSerialNumber = 10
},
)
.toTypedArray()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 7b26db5..5cdbe9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,7 +19,6 @@
import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
-import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
@@ -36,14 +35,17 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+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.fakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+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.model.CommunalContentModel
@@ -69,8 +71,6 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -114,7 +114,7 @@
private lateinit var communalRepository: FakeCommunalSceneRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeSmartspaceRepository
+ private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
@@ -135,7 +135,7 @@
communalRepository = kosmos.fakeCommunalSceneRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeSmartspaceRepository
+ smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
userRepository = kosmos.fakeUserRepository
keyguardRepository = kosmos.fakeKeyguardRepository
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
@@ -265,44 +265,6 @@
}
@Test
- fun smartspace_onlyShowTimersWithRemoteViews() =
- testScope.runTest {
- // Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
- // Not a timer
- val target1 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target1")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
- whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
- whenever(target1.creationTimeMillis).thenReturn(0L)
-
- // Does not have RemoteViews
- val target2 = mock(SmartspaceTarget::class.java)
- whenever(target2.smartspaceTargetId).thenReturn("target2")
- whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target2.remoteViews).thenReturn(null)
- whenever(target2.creationTimeMillis).thenReturn(0L)
-
- // Timer and has RemoteViews
- val target3 = mock(SmartspaceTarget::class.java)
- whenever(target3.smartspaceTargetId).thenReturn("target3")
- whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
- whenever(target3.creationTimeMillis).thenReturn(0L)
-
- val targets = listOf(target1, target2, target3)
- smartspaceRepository.setCommunalSmartspaceTargets(targets)
-
- val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
- assertThat(smartspaceContent?.size).isEqualTo(1)
- assertThat(smartspaceContent?.get(0)?.key)
- .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
- }
-
- @Test
fun smartspaceDynamicSizing_oneCard_fullSize() =
testSmartspaceDynamicSizing(
totalTargets = 1,
@@ -387,12 +349,12 @@
keyguardRepository.setKeyguardOccluded(false)
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- val targets = mutableListOf<SmartspaceTarget>()
+ val targets = mutableListOf<CommunalSmartspaceTimer>()
for (index in 0 until totalTargets) {
targets.add(smartspaceTimer(index.toString()))
}
- smartspaceRepository.setCommunalSmartspaceTargets(targets)
+ smartspaceRepository.setTimers(targets)
val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
@@ -441,18 +403,18 @@
// Timer1 started
val timer1 = smartspaceTimer("timer1", timestamp = 1L)
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))
+ smartspaceRepository.setTimers(listOf(timer1))
// Umo started
mediaRepository.mediaActive(timestamp = 2L)
// Timer2 started
val timer2 = smartspaceTimer("timer2", timestamp = 3L)
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))
+ smartspaceRepository.setTimers(listOf(timer1, timer2))
// Timer3 started
val timer3 = smartspaceTimer("timer3", timestamp = 4L)
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))
+ smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
val ongoingContent by collectLastValue(underTest.getOngoingContent(true))
assertThat(ongoingContent?.size).isEqualTo(4)
@@ -1089,13 +1051,12 @@
assertThat(showCommunalFromOccluded).isTrue()
}
- private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
- val timer = mock(SmartspaceTarget::class.java)
- whenever(timer.smartspaceTargetId).thenReturn(id)
- whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
- whenever(timer.creationTimeMillis).thenReturn(timestamp)
- return timer
+ private fun smartspaceTimer(id: String, timestamp: Long = 0L): CommunalSmartspaceTimer {
+ return CommunalSmartspaceTimer(
+ smartspaceTargetId = id,
+ createdTimestampMillis = timestamp,
+ remoteViews = mock(RemoteViews::class.java)
+ )
}
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
index 0cd3fb2..d51d356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -26,14 +26,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+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.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -41,6 +50,7 @@
@RunWith(AndroidJUnit4::class)
class SmartspaceInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -51,29 +61,43 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: SmartspaceInteractionHandler by lazy {
- SmartspaceInteractionHandler(activityStarter)
+ private lateinit var underTest: SmartspaceInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForSmartspaceView() {
- val parent = FrameLayout(context)
- val view = SmartspaceAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = SmartspaceAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentWithoutDismissing(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+ }
}
@Test
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 0190ccb..b138fb3 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
@@ -16,7 +16,6 @@
package com.android.systemui.communal.view.viewmodel
-import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.Intent
@@ -32,12 +31,16 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
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.communalPrefsInteractor
@@ -57,8 +60,6 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
@@ -76,6 +77,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -91,9 +94,10 @@
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeSmartspaceRepository
+ private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var communalSceneInteractor: CommunalSceneInteractor
+ private lateinit var communalInteractor: CommunalInteractor
private val testableResources = context.orCreateTestableResources
@@ -105,9 +109,10 @@
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeSmartspaceRepository
+ 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),
@@ -119,7 +124,7 @@
underTest =
CommunalEditModeViewModel(
communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.keyguardTransitionInteractor,
mediaHost,
@@ -152,11 +157,15 @@
widgetRepository.setCommunalWidgets(widgets)
// Smartspace available.
- val target = Mockito.mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ smartspaceRepository.setTimers(
+ listOf(
+ CommunalSmartspaceTimer(
+ smartspaceTargetId = "target",
+ createdTimestampMillis = 0L,
+ remoteViews = Mockito.mock(RemoteViews::class.java),
+ )
+ )
+ )
// Media playing.
mediaRepository.mediaActive()
@@ -342,6 +351,16 @@
assertThat(showDisclaimer).isFalse()
}
+ @Test
+ fun scrollPosition_persistedOnEditCleanup() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.cleanupEditModeState()
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
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 d338774..c480aa8 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
@@ -16,7 +16,6 @@
package com.android.systemui.communal.view.viewmodel
-import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -27,14 +26,18 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+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.fakeCommunalSceneRepository
+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.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -78,8 +81,6 @@
import com.android.systemui.shade.ShadeTestUtil
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -97,7 +98,9 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -115,12 +118,13 @@
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeSmartspaceRepository
+ private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
+ private lateinit var communalInteractor: CommunalInteractor
private lateinit var underTest: CommunalViewModel
@@ -136,7 +140,7 @@
keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
tutorialRepository = kosmos.fakeCommunalTutorialRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeSmartspaceRepository
+ smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
shadeTestUtil = kosmos.shadeTestUtil
@@ -154,6 +158,8 @@
kosmos.powerInteractor.setAwakeForTest()
+ communalInteractor = spy(kosmos.communalInteractor)
+
underTest =
CommunalViewModel(
kosmos.testDispatcher,
@@ -164,7 +170,7 @@
kosmos.keyguardInteractor,
mock<KeyguardIndicationController>(),
kosmos.communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -222,11 +228,15 @@
widgetRepository.setCommunalWidgets(widgets)
// Smartspace available.
- val target = Mockito.mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ smartspaceRepository.setTimers(
+ listOf(
+ CommunalSmartspaceTimer(
+ smartspaceTargetId = "target",
+ createdTimestampMillis = 0L,
+ remoteViews = Mockito.mock(RemoteViews::class.java),
+ )
+ )
+ )
// Media playing.
mediaRepository.mediaActive()
@@ -293,7 +303,7 @@
widgetRepository.setCommunalWidgets(emptyList())
// UMO playing
mediaRepository.mediaActive()
- smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+ smartspaceRepository.setTimers(emptyList())
val isEmptyState by collectLastValue(underTest.isEmptyState)
assertThat(isEmptyState).isTrue()
@@ -314,7 +324,7 @@
),
)
mediaRepository.mediaInactive()
- smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+ smartspaceRepository.setTimers(emptyList())
val isEmptyState by collectLastValue(underTest.isEmptyState)
assertThat(isEmptyState).isFalse()
@@ -689,11 +699,15 @@
advanceTimeBy(60L)
// New timer available
- val target = Mockito.mock(SmartspaceTarget::class.java)
- whenever<String?>(target.smartspaceTargetId).thenReturn("target")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ smartspaceRepository.setTimers(
+ listOf(
+ CommunalSmartspaceTimer(
+ smartspaceTargetId = "target",
+ createdTimestampMillis = 0L,
+ remoteViews = Mockito.mock(RemoteViews::class.java),
+ )
+ )
+ )
runCurrent()
// Still only emits widgets and the CTA tile
@@ -748,11 +762,15 @@
assertThat(communalContent).hasSize(3)
// When new timer available
- val target = Mockito.mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
- smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ smartspaceRepository.setTimers(
+ listOf(
+ CommunalSmartspaceTimer(
+ smartspaceTargetId = "target",
+ createdTimestampMillis = 0L,
+ remoteViews = Mockito.mock(RemoteViews::class.java),
+ )
+ )
+ )
runCurrent()
// Then emits timer, widgets and the CTA tile
@@ -767,6 +785,16 @@
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
+ @Test
+ fun scrollPosition_persistedOnEditEntry() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.onOpenWidgetEditor(false)
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
new file mode 100644
index 0000000..ac50db4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+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.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
+ private val controller = mock<ActivityTransitionAnimator.Controller>()
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: CommunalTransitionAnimatorController
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = CommunalTransitionAnimatorController(controller, communalSceneInteractor)
+ }
+ }
+
+ @Test
+ fun doNotAnimate_launchingWidgetStateIsCleared() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = false)
+ assertFalse(launching!!)
+ verify(controller).onIntentStarted(willAnimate = false)
+ }
+ }
+ }
+
+ @Test
+ fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ }
+ }
+ }
+
+ @Test
+ fun animationComplete_launchingWidgetStateIsClearedAndSceneIsChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ verify(controller).onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 7044895..ea8b5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -26,13 +26,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+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.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -40,6 +48,7 @@
@RunWith(AndroidJUnit4::class)
class WidgetInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -50,30 +59,44 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: WidgetInteractionHandler by lazy {
- WidgetInteractionHandler(activityStarter)
+ private lateinit var underTest: WidgetInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForWidgetView() {
- val parent = FrameLayout(context)
- val view = CommunalAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = CommunalAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentMaybeDismissingKeyguard(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- /* customMessage */ isNull(),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 546510b..3253edf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
-import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,27 +31,14 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -61,7 +47,6 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -438,231 +423,6 @@
assertThat(isUnlocked).isTrue()
}
- @Test
- fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
- TrustAgentDisabled,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- TrustAgentDisabled,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
- testScope.runTest {
- val deviceEntryRestrictionReason by
- collectLastValue(underTest.deviceEntryRestrictionReason)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
- )
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(
- userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- )
- )
- runCurrent()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason).isNull()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
- }
-
- @Test
- fun reportUserPresent_whenDeviceEntered() =
- testScope.runTest {
- val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(0)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- switchToScene(Scenes.Lockscreen)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(2)
- }
-
- private fun TestScope.verifyRestrictionReasonsForAuthFlags(
- vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
- ) {
- val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
-
- authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(userId = 1, flag = flag)
- )
- runCurrent()
-
- if (expectedReason == null) {
- assertThat(deviceEntryRestrictionReason).isNull()
- } else {
- assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
- }
- }
- }
-
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index a7a7bea3..c2acc5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -19,17 +19,20 @@
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -40,6 +43,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -54,18 +58,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationRepository = kosmos.fakeAuthenticationRepository
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- val underTest =
- DeviceUnlockedInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = kosmos.authenticationInteractor,
- deviceEntryRepository = deviceEntryRepository,
- trustInteractor = kosmos.trustInteractor,
- faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
- powerInteractor = kosmos.powerInteractor,
- )
+ val underTest = kosmos.deviceUnlockedInteractor
@Before
fun setup() {
@@ -100,6 +94,30 @@
}
@Test
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPinAndInLockdown_isFalse() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ val isInLockdown by collectLastValue(underTest.isInLockdown)
+
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag =
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ )
+ )
+ runCurrent()
+ assertThat(isInLockdown).isTrue()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
+ }
+
+ @Test
fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
@@ -221,6 +239,218 @@
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_whenLockdown() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5a39de8..60c9bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -34,10 +34,14 @@
import androidx.lifecycle.LifecycleRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.ViewCaptureFactory
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -79,6 +83,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -154,8 +159,12 @@
@Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
+ @Mock lateinit var mLazyViewCapture: Lazy<ViewCapture>
+
+ private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
+ private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
@@ -192,13 +201,20 @@
whenever(mDreamOverlayContainerViewController.containerView)
.thenReturn(mDreamOverlayContainerView)
whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
+ whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
mWindowParams = WindowManager.LayoutParams()
+ mViewCaptureAwareWindowManager =
+ ViewCaptureAwareWindowManager(
+ mWindowManager,
+ mLazyViewCapture,
+ isViewCaptureEnabled = false
+ )
mService =
DreamOverlayService(
mContext,
mLifecycleOwner,
mMainExecutor,
- mWindowManager,
+ mViewCaptureAwareWindowManager,
mComplicationComponentFactory,
mDreamComplicationComponentFactory,
mDreamOverlayComponentFactory,
@@ -623,7 +639,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -647,7 +663,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 4a5342a..3a4b14b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.shared.education.GestureType.BACK_GESTURE
import com.google.common.truth.Truth.assertThat
import java.io.File
+import java.time.Clock
+import java.time.Instant
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -48,6 +50,7 @@
private val dsScopeProvider: Provider<CoroutineScope> = Provider {
TestScope(kosmos.testDispatcher).backgroundScope
}
+ private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
private val testUserId = 1111
// For deleting any test files created after the test
@@ -59,7 +62,7 @@
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
- underTest = ContextualEducationRepository(userRepository)
+ underTest = ContextualEducationRepositoryImpl(clock, userRepository)
underTest.setUser(testUserId)
}
@@ -85,6 +88,15 @@
assertThat(model?.signalCount).isEqualTo(1)
}
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+ underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 74eee9b..693fcda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -17,10 +17,12 @@
package com.android.systemui.haptics.qs
import android.os.VibrationEffect
+import android.service.quicksettings.Tile
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.qsTileFactory
@@ -69,6 +71,7 @@
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
+ kosmos.falsingManager,
)
longPressEffect.callback = callback
longPressEffect.qsTile = qsTile
@@ -175,17 +178,17 @@
}
@Test
- fun onAnimationComplete_keyguardDismissible_effectCompletes() =
+ fun onAnimationComplete_keyguardDismissible_effectEndsInLongClicked() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the long-press effect completes
- assertEffectCompleted()
+ // THEN the long-press effect completes with a long-click state
+ assertEffectCompleted(QSLongPressEffect.State.LONG_CLICKED)
}
@Test
- fun onAnimationComplete_keyguardNotDismissible_effectEndsWithReset() =
+ fun onAnimationComplete_keyguardNotDismissible_effectEndsInIdleWithReset() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN that the keyguard is not dismissible
whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
@@ -193,19 +196,20 @@
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the long-press effect completes and the properties are called to reset
- assertEffectCompleted()
+ // THEN the long-press effect ends in the idle state and the properties are reset
+ assertEffectCompleted(QSLongPressEffect.State.IDLE)
verify(callback, times(1)).onResetProperties()
}
@Test
- fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() =
+ fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the callback for finished reversing is used.
+ // THEN the callback for finished reversing is used and the effect ends with a click.
verify(callback, times(1)).onEffectFinishedReversing()
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.CLICKED)
}
@Test
@@ -262,18 +266,88 @@
}
@Test
- fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
- testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
- // GIVEN that no QSTile has been set
- longPressEffect.qsTile = null
-
+ fun onTileClick_whileIdle_withQSTile_clicks() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
// GIVEN that a click was detected
val couldClick = longPressEffect.onTileClick()
+ // THEN the click is successful
+ assertThat(couldClick).isTrue()
+ }
+
+ @Test
+ fun onTileClick_whenBouncerIsShowing_ignoresClick() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
+ // GIVEN that the bouncer is showing
+ whenever(kosmos.keyguardStateController.isPrimaryBouncerShowing).thenReturn(true)
+
+ // WHEN a click is detected by the tile view
+ val couldClick = longPressEffect.onTileClick()
+
// THEN the click is not successful
assertThat(couldClick).isFalse()
}
+ @Test
+ fun getStateForClick_withUnavailableTile_returnsIdle() {
+ // GIVEN an unavailable tile
+ qsTile.state?.state = Tile.STATE_UNAVAILABLE
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ @Test
+ fun getStateForClick_withFalseTapWhenLocked_returnsIdle() {
+ // GIVEN an active tile
+ qsTile.state?.state = Tile.STATE_ACTIVE
+
+ // GIVEN that the device is locked and a false tap is detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(true)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ @Test
+ fun getStateForClick_withValidTapAndTile_returnsClicked() {
+ // GIVEN an active tile
+ qsTile.state?.state = Tile.STATE_ACTIVE
+
+ // GIVEN that the device is locked and a false tap is not detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(false)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is CLICKED
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.CLICKED)
+ }
+
+ @Test
+ fun getStateForClick_withNullTile_returnsIdle() {
+ // GIVEN that the tile is null
+ longPressEffect.qsTile = null
+
+ // GIVEN that the device is locked and a false tap is not detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(false)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
@@ -339,14 +413,14 @@
/**
* Asserts that the effect completes by checking that:
* 1. The final snap haptics are played
- * 2. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 2. The internal state goes back to specified end state.
*/
- private fun assertEffectCompleted() {
+ private fun assertEffectCompleted(endState: QSLongPressEffect.State) {
val snapEffect = LongPressHapticBuilder.createSnapEffect()
assertThat(snapEffect).isNotNull()
assertThat(vibratorHelper.hasVibratedWithEffects(snapEffect!!)).isTrue()
- assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(longPressEffect.state).isEqualTo(endState)
}
/**
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 612f2e7..b885800 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
@@ -34,12 +34,14 @@
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
+import android.service.dream.dreamManager
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.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,8 +66,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -119,6 +123,66 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, Flags.FLAG_COMMUNAL_HUB)
+ fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // 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(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is NOT possible but communal is available, then we should transition to
+ // LOCKSCREEN when waking up due to power button press.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(false)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is possible but communal is NOT available, then we should transition to
+ // LOCKSCREEN when waking up due to power button press.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 26b56a1..4862104 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -37,7 +37,6 @@
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -81,10 +80,10 @@
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
kosmos.keyguardInteractor.setClockShouldBeCentered(true)
- assertThat(value).isEqualTo(true)
+ assertThat(value).isTrue()
kosmos.keyguardInteractor.setClockShouldBeCentered(false)
- assertThat(value).isEqualTo(false)
+ assertThat(value).isFalse()
}
@Test
@@ -103,7 +102,7 @@
fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
assertThat(value).isEqualTo(ClockSize.SMALL)
}
@@ -113,7 +112,7 @@
fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
val userMedia = MediaData().copy(active = true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
assertThat(value).isEqualTo(ClockSize.SMALL)
@@ -125,7 +124,7 @@
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
val userMedia = MediaData().copy(active = true)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
kosmos.keyguardRepository.setIsDozing(false)
assertThat(value).isEqualTo(ClockSize.SMALL)
@@ -136,7 +135,7 @@
fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.keyguardRepository.setIsDozing(false)
assertThat(value).isEqualTo(ClockSize.LARGE)
}
@@ -147,7 +146,7 @@
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
val userMedia = MediaData().copy(active = true)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
kosmos.keyguardRepository.setIsDozing(true)
assertThat(value).isEqualTo(ClockSize.LARGE)
@@ -158,8 +157,8 @@
fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
- assertThat(value).isEqualTo(true)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+ assertThat(value).isTrue()
}
@Test
@@ -167,9 +166,9 @@
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_noActiveNotifications_true() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(0)
- assertThat(value).isEqualTo(true)
+ assertThat(value).isTrue()
}
@Test
@@ -177,10 +176,10 @@
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- assertThat(value).isEqualTo(true)
+ assertThat(value).isTrue()
}
@Test
@@ -188,11 +187,11 @@
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
kosmos.headsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
kosmos.keyguardRepository.setIsDozing(true)
- assertThat(value).isEqualTo(false)
+ assertThat(value).isFalse()
}
@Test
@@ -200,10 +199,10 @@
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_onAod_true() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
- assertThat(value).isEqualTo(true)
+ assertThat(value).isTrue()
}
@Test
@@ -211,10 +210,10 @@
fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_offAod_false() =
testScope.runTest {
val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
- assertThat(value).isEqualTo(false)
+ assertThat(value).isFalse()
}
private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 875e9e0..3075c54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,7 +38,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -76,7 +77,7 @@
fun setup() {
with(kosmos) {
fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
underTest = lockscreenContentViewModel
}
}
@@ -125,8 +126,51 @@
fun areNotificationsVisible_splitShadeTrue_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnNotificationsShade_false() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.NotificationsShade))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isFalse()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnQuickSettingsShade_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.QuickSettingsShade))
+ shadeRepository.setShadeLayoutWide(true)
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(areNotificationsVisible).isTrue()
@@ -138,7 +182,8 @@
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
assertThat(areNotificationsVisible).isTrue()
}
@@ -149,31 +194,32 @@
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(areNotificationsVisible).isFalse()
}
}
@Test
- fun shouldUseSplitNotificationShade_withConfigTrue_true() =
+ fun isShadeLayoutWide_withConfigTrue_true() =
with(kosmos) {
testScope.runTest {
- val shouldUseSplitNotificationShade by
- collectLastValue(underTest.shouldUseSplitNotificationShade)
- shadeRepository.setShadeMode(ShadeMode.Split)
- assertThat(shouldUseSplitNotificationShade).isTrue()
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(true)
+
+ assertThat(isShadeLayoutWide).isTrue()
}
}
@Test
- fun shouldUseSplitNotificationShade_withConfigFalse_false() =
+ fun isShadeLayoutWide_withConfigFalse_false() =
with(kosmos) {
testScope.runTest {
- val shouldUseSplitNotificationShade by
- collectLastValue(underTest.shouldUseSplitNotificationShade)
- shadeRepository.setShadeMode(ShadeMode.Single)
- assertThat(shouldUseSplitNotificationShade).isFalse()
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(false)
+
+ assertThat(isShadeLayoutWide).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 4eb146d..3db9ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -44,7 +44,6 @@
import com.android.systemui.scene.shared.model.TransitionKeys
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
@@ -182,13 +181,7 @@
}
)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- kosmos.shadeRepository.setShadeMode(
- if (isSingleShade) {
- ShadeMode.Single
- } else {
- ShadeMode.Split
- }
- )
+ kosmos.shadeRepository.setShadeLayoutWide(!isSingleShade)
kosmos.setCommunalAvailable(isCommunalAvailable)
kosmos.fakePowerRepository.updateWakefulness(
rawState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
new file mode 100644
index 0000000..188f2ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs
+
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+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.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.settings.SettingsProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SettingObserverTest : SysuiTestCase() {
+
+ private val DEFAULT_VALUE = 7
+
+ @Mock lateinit var settingsProxy: SettingsProxy
+ @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+ private lateinit var testSettingObserver: SettingObserver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(settingsProxy.getInt(any(), any())).thenReturn(5)
+ whenever(settingsProxy.getUriFor(any())).thenReturn(Uri.parse("content://test_uri"))
+ testSettingObserver =
+ object :
+ SettingObserver(
+ settingsProxy,
+ Handler(Looper.getMainLooper()),
+ "test_setting",
+ DEFAULT_VALUE
+ ) {
+ override fun handleValueChanged(value: Int, observedChange: Boolean) {}
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+ fun setListening_true_settingsProxyRegistered() {
+ testSettingObserver.isListening = true
+ verify(settingsProxy)
+ .registerContentObserverAsync(
+ any<Uri>(),
+ eq(false),
+ eq(testSettingObserver),
+ capture(argumentCaptor)
+ )
+ assertThat(testSettingObserver.value).isEqualTo(5)
+
+ // Verify if the callback applies updated value after the fact
+ whenever(settingsProxy.getInt(any(), any())).thenReturn(12341234)
+ argumentCaptor.value.run()
+ assertThat(testSettingObserver.value).isEqualTo(12341234)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+ fun setListening_false_settingsProxyRegistered() {
+ testSettingObserver.isListening = true
+ reset(settingsProxy)
+ testSettingObserver.isListening = false
+
+ verify(settingsProxy).unregisterContentObserverAsync(eq(testSettingObserver))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+ fun setListening_bgFlagDisabled_true_settingsProxyRegistered() {
+ testSettingObserver.isListening = true
+ verify(settingsProxy)
+ .registerContentObserverSync(any<Uri>(), eq(false), eq(testSettingObserver))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+ fun setListening_bgFlagDisabled_false_settingsProxyRegistered() {
+ testSettingObserver.isListening = true
+ reset(settingsProxy)
+ testSettingObserver.isListening = false
+
+ verify(settingsProxy).unregisterContentObserverSync(eq(testSettingObserver))
+ }
+}
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 8ac5b6c..dda9cd5 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
@@ -24,6 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.settings.userFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -40,6 +41,70 @@
private val underTest = with(kosmos) { qsPreferencesRepository }
@Test
+ fun largeTilesSpecs_updatesFromSharedPreferences() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(underTest.largeTilesSpecs)
+ assertThat(latest).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+
+ val newSet = setOf("tileA", "tileB")
+ setLargeTilesSpecsInSharedPreferences(newSet)
+ assertThat(latest).isEqualTo(newSet.toTileSpecs())
+ }
+ }
+
+ @Test
+ fun largeTilesSpecs_updatesFromUserChange() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeUserRepository.setUserInfos(USERS)
+ val latest by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val newSet = setOf("tileA", "tileB")
+ setLargeTilesSpecsInSharedPreferences(newSet)
+
+ fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
+ setLargeTilesSpecsInSharedPreferences(emptySet())
+
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ assertThat(latest).isEqualTo(newSet.toTileSpecs())
+ }
+ }
+
+ @Test
+ fun setLargeTilesSpecs_inSharedPreferences() {
+ val setA = setOf("tileA", "tileB")
+ underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+
+ val setB = setOf("tileA", "tileB")
+ underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
+ }
+
+ @Test
+ fun setLargeTilesSpecs_forDifferentUser() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeUserRepository.setUserInfos(USERS)
+
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val setA = setOf("tileA", "tileB")
+ underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+
+ fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
+ val setB = setOf("tileA", "tileB")
+ underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
+
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
+ }
+ }
+
+ @Test
fun showLabels_updatesFromSharedPreferences() =
with(kosmos) {
testScope.runTest {
@@ -109,6 +174,14 @@
)
}
+ private fun setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+ getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+ }
+
+ private fun getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+ return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+ }
+
private fun setShowLabelsInSharedPreferences(value: Boolean) {
getSharedPreferences().edit().putBoolean(ICON_LABELS_KEY, value).apply()
}
@@ -117,8 +190,13 @@
return getSharedPreferences().getBoolean(ICON_LABELS_KEY, defaultValue)
}
+ private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+ return map { TileSpec.create(it) }.toSet()
+ }
+
companion object {
private const val ICON_LABELS_KEY = "show_icon_labels"
+ private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
private const val PRIMARY_USER_ID = 0
private val PRIMARY_USER = UserInfo(PRIMARY_USER_ID, "user 0", UserInfo.FLAG_MAIN)
private const val ANOTHER_USER_ID = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index b206f56..56b3679 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -20,9 +20,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
@@ -42,22 +42,17 @@
@RunWith(AndroidJUnit4::class)
class GridConsistencyInteractorTest : SysuiTestCase() {
- private val iconOnlyTiles =
- setOf(
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- )
-
private val kosmos =
testKosmos().apply {
- iconTilesRepository =
- object : IconTilesRepository {
- override fun isIconTile(spec: TileSpec): Boolean {
- return iconOnlyTiles.contains(spec)
- }
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles =
+ setOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
}
}
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
new file mode 100644
index 0000000..c3a5df06
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconTilesInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
+ }
+ }
+ private val underTest = with(kosmos) { iconTilesInteractor }
+
+ @Test
+ fun isIconTile_returnsCorrectValue() {
+ assertThat(underTest.isIconTile(TileSpec.create("large"))).isFalse()
+ assertThat(underTest.isIconTile(TileSpec.create("small"))).isTrue()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun isIconTile_updatesFromSharedPreferences() =
+ with(kosmos) {
+ testScope.runTest {
+ // Assert that new tile defaults to icon
+ assertThat(underTest.isIconTile(TileSpec.create("newTile"))).isTrue()
+
+ qsPreferencesRepository.setLargeTilesSpecs(setOf(TileSpec.create("newTile")))
+ runCurrent()
+
+ // Assert that the new tile was added to the large tiles set
+ assertThat(underTest.isIconTile(TileSpec.create("newTile"))).isFalse()
+ }
+ }
+
+ @Test
+ fun resize_updatesSharedPreferences() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(qsPreferencesRepository.largeTilesSpecs)
+ val spec = TileSpec.create("large")
+
+ // Assert that the tile is added to the large tiles after resizing
+ underTest.resize(spec, toIcon = false)
+ assertThat(latest).contains(spec)
+
+ // Assert that the tile is removed from the large tiles after resizing
+ underTest.resize(spec, toIcon = true)
+ assertThat(latest).doesNotContain(spec)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
index 1e2e82f..ea51398 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -20,8 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -33,21 +33,17 @@
@RunWith(AndroidJUnit4::class)
class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
- private val iconOnlyTiles =
- setOf(
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- )
private val kosmos =
testKosmos().apply {
- iconTilesRepository =
- object : IconTilesRepository {
- override fun isIconTile(spec: TileSpec): Boolean {
- return iconOnlyTiles.contains(spec)
- }
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles: Set<TileSpec> =
+ setOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
}
}
private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 1c3021e..73a0039 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -82,6 +82,20 @@
TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
}
+ @Test
+ fun onMoveOutOfBounds_removeMovingTileFromCurrentList() {
+ val movingTileSpec = TestEditTiles[0].tileSpec
+
+ // Start the drag movement
+ underTest.onStarted(movingTileSpec)
+
+ // Move the tile outside of the list
+ underTest.movedOutOfBounds()
+
+ // Asserts the moving tile is not current
+ assertThat(listState.tiles.first { it.tileSpec == movingTileSpec }.isCurrent).isFalse()
+ }
+
companion object {
private fun createEditTile(tileSpec: String): EditTileViewModel {
return EditTileViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 914a095..53384af 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -20,8 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
@@ -37,11 +37,9 @@
class InfiniteGridLayoutTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- iconTilesRepository =
- object : IconTilesRepository {
- override fun isIconTile(spec: TileSpec): Boolean {
- return spec.spec.startsWith("small")
- }
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
index 3354b4d4..55b7454 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
@@ -20,8 +20,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
@@ -37,11 +37,9 @@
class PartitionedGridLayoutTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- iconTilesRepository =
- object : IconTilesRepository {
- override fun isIconTile(spec: TileSpec): Boolean {
- return spec.spec.startsWith("small")
- }
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
index d36a81b..56156a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -24,8 +24,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.res.R
@@ -40,18 +40,6 @@
@RunWith(AndroidJUnit4::class)
class QuickQuickSettingsViewModelTest : SysuiTestCase() {
- private val kosmos =
- testKosmos().apply {
- iconTilesRepository =
- object : IconTilesRepository {
- override fun isIconTile(spec: TileSpec): Boolean {
- return spec.spec.startsWith(PREFIX_SMALL)
- }
- }
- }
-
- private val underTest = kosmos.quickQuickSettingsViewModel
-
private val tiles =
listOf(
"$PREFIX_SMALL:1",
@@ -66,6 +54,17 @@
)
.map(TileSpec::create)
+ private val kosmos =
+ testKosmos().apply {
+ defaultLargeTilesRepository =
+ object : DefaultLargeTilesRepository {
+ override val defaultLargeTiles: Set<TileSpec> =
+ tiles.filter { it.spec.startsWith(PREFIX_LARGE) }.toSet()
+ }
+ }
+
+ private val underTest = kosmos.quickQuickSettingsViewModel
+
@Before
fun setUp() {
kosmos.setTiles(tiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
index 89b9b7f..67e2fba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
import android.os.UserHandle
import android.platform.test.annotations.EnabledOnRavenwood
@@ -23,7 +23,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 8982d81..79fcc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
@@ -26,7 +26,6 @@
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject.Companion.assertThat
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,7 +53,7 @@
connectivityRepository,
mobileConnectionsRepository,
),
- inputHandler
+ inputHandler,
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
new file mode 100644
index 0000000..09dca25
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.qs.tiles.impl.modes.domain.interactor
+
+import android.app.Flags
+import android.os.UserHandle
+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.settingslib.notification.data.repository.FakeZenModeRepository
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModesTileDataInteractorTest : SysuiTestCase() {
+ private val zenModeRepository = FakeZenModeRepository()
+
+ private val underTest = ModesTileDataInteractor(zenModeRepository)
+
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ @Test
+ fun availableWhenFlagIsOn() = runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).containsExactly(true)
+ }
+
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ @Test
+ fun unavailableWhenFlagIsOff() = runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).containsExactly(false)
+ }
+
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ @Test
+ fun isActivatedWhenModesChange() = runTest {
+ val dataList: List<ModesTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
+
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+
+ // Add another mode: state hasn't changed, so this shouldn't cause another emission
+ zenModeRepository.addMode(id = "Two", active = true)
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+
+ // Remove a mode and disable the other
+ zenModeRepository.removeMode("One")
+ runCurrent()
+ zenModeRepository.deactivateMode("Two")
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false).inOrder()
+ }
+
+ private companion object {
+
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
index 2e5fde8..a5f98a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
@@ -30,7 +30,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.toCollection
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -56,8 +55,7 @@
@Test
fun alwaysAvailable() =
testScope.runTest {
- val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
-
+ val availability by collectValues(underTest.availability(TEST_USER))
assertThat(availability).hasSize(1)
assertThat(availability.last()).isEqualTo(isAvailable)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
index 6ea5e63..3133312 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
@@ -17,9 +17,13 @@
package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
import android.platform.test.annotations.EnabledOnRavenwood
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.server.display.feature.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.reduceBrightColorsController
import com.android.systemui.kosmos.Kosmos
@@ -43,11 +47,22 @@
private val underTest =
ReduceBrightColorsTileUserActionInteractor(
+ context.resources,
+ inputHandler,
+ controller,
+ )
+
+ private val underTestEvenDimmerEnabled =
+ ReduceBrightColorsTileUserActionInteractor(
+ context.orCreateTestableResources
+ .apply { addOverride(R.bool.config_evenDimmerEnabled, true) }
+ .resources,
inputHandler,
controller,
)
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
fun handleClickWhenEnabled() = runTest {
val wasEnabled = true
controller.isReduceBrightColorsActivated = wasEnabled
@@ -58,6 +73,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
fun handleClickWhenDisabled() = runTest {
val wasEnabled = false
controller.isReduceBrightColorsActivated = wasEnabled
@@ -68,6 +84,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
fun handleLongClickWhenDisabled() = runTest {
val enabled = false
@@ -79,6 +96,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
fun handleLongClickWhenEnabled() = runTest {
val enabled = true
@@ -88,4 +106,58 @@
assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
}
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun handleClickWhenEnabledEvenDimmer() = runTest {
+ val wasEnabled = true
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTestEvenDimmerEnabled.handleInput(
+ QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled))
+ )
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(wasEnabled)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun handleClickWhenDisabledEvenDimmer() = runTest {
+ val wasEnabled = false
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTestEvenDimmerEnabled.handleInput(
+ QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled))
+ )
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(wasEnabled)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun handleLongClickWhenDisabledEvenDimmer() = runTest {
+ val enabled = false
+
+ underTestEvenDimmerEnabled.handleInput(
+ QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_DISPLAY_SETTINGS)
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun handleLongClickWhenEnabledEvenDimmer() = runTest {
+ val enabled = true
+
+ underTestEvenDimmerEnabled.handleInput(
+ QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_DISPLAY_SETTINGS)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index b35b7bc..09580c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -38,7 +38,6 @@
import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -558,7 +557,7 @@
fun dispatchSplitShade() =
testScope.runTest {
val shadeRepository = kosmos.fakeShadeRepository
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
val qsImpl by collectLastValue(underTest.qsImpl)
underTest.inflate(context)
@@ -566,7 +565,7 @@
verify(qsImpl!!).setInSplitShade(false)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
runCurrent()
verify(qsImpl!!).setInSplitShade(true)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 3acb328..e3a69a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -49,6 +49,7 @@
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,7 +64,8 @@
private val underTest = kosmos.sceneInteractor
- init {
+ @Before
+ fun setUp() {
// Init lazy Fixtures. Accessing them once makes sure that the singletons are initialized
// and therefore starts to collect StateFlows eagerly (when there are any).
kosmos.deviceUnlockedInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index b5e47d1..fd1b213 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -85,7 +85,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -182,7 +181,6 @@
kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
buildNotificationRows(isPinned = false)
- advanceTimeBy(50L) // account for HeadsUpNotificationInteractor debounce
assertThat(isVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 545a0c7..0ab6a82 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +62,7 @@
fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
runCurrent()
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
@@ -74,7 +73,7 @@
fun downTransitionKey_splitShadeDisabled_isNull() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
runCurrent()
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 78c4def..3283ea1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -19,6 +19,8 @@
import android.app.StatusBarManager.DISABLE2_NONE
import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,7 +39,10 @@
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.phone.dozeParameters
@@ -452,4 +457,44 @@
val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
assertThat(isShadeTouchable).isTrue()
}
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun legacyShadeMode_narrowScreen_singleShade() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun legacyShadeMode_wideScreen_splitShade() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun shadeMode_wideScreen_isDual() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun shadeMode_narrowScreen_isDual() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index b1bffdb..8a43198 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
@@ -568,21 +567,6 @@
sceneInteractor.setTransitionState(transitionState)
// THEN interacting is false
- Truth.assertThat(interacting).isFalse()
- }
-
- @Test
- fun shadeMode() =
- testScope.runTest {
- val shadeMode by collectLastValue(underTest.shadeMode)
-
- shadeRepository.setShadeMode(ShadeMode.Split)
- assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-
- shadeRepository.setShadeMode(ShadeMode.Single)
- assertThat(shadeMode).isEqualTo(ShadeMode.Single)
-
- shadeRepository.setShadeMode(ShadeMode.Split)
- assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ assertThat(interacting).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 673d5ef..da22c6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -38,7 +39,6 @@
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
@@ -47,6 +47,7 @@
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
@@ -66,6 +67,7 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
+@DisableFlags(DualShade.FLAG_NAME)
class ShadeSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -157,7 +159,7 @@
fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
runCurrent()
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
@@ -168,7 +170,7 @@
fun upTransitionKey_splitShadeDisable_isNull() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
runCurrent()
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
@@ -268,13 +270,13 @@
testScope.runTest {
val shadeMode by collectLastValue(underTest.shadeMode)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
assertThat(shadeMode).isEqualTo(ShadeMode.Single)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 5ef3485..8b4265f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -280,64 +279,6 @@
}
@Test
- fun isHeadsUpOrAnimatingAway_falseOnStart() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isFalse()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_hasPinnedRows() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAway() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN the last row is animating away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAwayDebounced() =
- testScope.runTest {
- val values by collectValues(underTest.isHeadsUpOrAnimatingAway)
-
- // GIVEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
- assertThat(values.size).isEqualTo(2)
- assertThat(values.first()).isFalse() // initial value
- assertThat(values.last()).isTrue()
-
- // WHEN the last row is removed
- headsUpRepository.setNotifications(emptyList())
- runCurrent()
- // AND starts to animate away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- // THEN isHeadsUpOrAnimatingAway remained true
- assertThat(values.size).isEqualTo(2)
- assertThat(values.last()).isTrue()
- }
-
- @Test
fun showHeadsUpStatusBar_true() =
testScope.runTest {
val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index 50b77dc..1356e93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -22,7 +22,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
import com.android.systemui.testKosmos
@@ -66,11 +65,11 @@
testScope.runTest {
val stackRounding by collectLastValue(underTest.shadeScrimRounding)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
assertThat(stackRounding)
.isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
assertThat(stackRounding)
.isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index ba7ddce..f8e6337 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -633,7 +633,7 @@
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun headsUpAnimationsEnabled_keyguardShowing_false() =
+ fun headsUpAnimationsEnabled_keyguardShowing_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -641,6 +641,6 @@
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index ccd78ee..59e8ea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -136,7 +137,9 @@
communalSceneInteractor = communalSceneInteractor,
)
`when`(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false))
`when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false))
}
@Test
@@ -335,6 +338,102 @@
)
}
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ val runnableCaptor = argumentCaptor<Runnable>()
+ verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
@Test
fun startActivity_noUserHandleProvided_getUserHandle() {
val intent = mock(Intent::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
deleted file mode 100644
index 3d3438e..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ /dev/null
@@ -1,243 +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.systemui.statusbar.policy;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Handler;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import kotlinx.coroutines.flow.StateFlowKt;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
-@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
- @Rule public MockitoRule rule = MockitoJUnit.rule();
-
- private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
- logcatLogBuffer());
- @Mock private GroupMembershipManager mGroupManager;
- @Mock private VisualStabilityProvider mVSProvider;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private KeyguardBypassController mBypassController;
- @Mock private ConfigurationControllerImpl mConfigurationController;
- @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- @Mock private UiEventLogger mUiEventLogger;
- @Mock private JavaAdapter mJavaAdapter;
- @Mock private ShadeInteractor mShadeInteractor;
- @Mock private DumpManager dumpManager;
- private AvalancheController mAvalancheController;
-
- @Mock private Handler mBgHandler;
-
- private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
- TestableHeadsUpManagerPhone(
- Context context,
- HeadsUpManagerLogger headsUpManagerLogger,
- GroupMembershipManager groupManager,
- VisualStabilityProvider visualStabilityProvider,
- StatusBarStateController statusBarStateController,
- KeyguardBypassController keyguardBypassController,
- ConfigurationController configurationController,
- GlobalSettings globalSettings,
- SystemClock systemClock,
- DelayableExecutor executor,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger,
- JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor,
- AvalancheController avalancheController
- ) {
- super(
- context,
- headsUpManagerLogger,
- statusBarStateController,
- keyguardBypassController,
- groupManager,
- visualStabilityProvider,
- configurationController,
- mockExecutorHandler(executor),
- globalSettings,
- systemClock,
- executor,
- accessibilityManagerWrapper,
- uiEventLogger,
- javaAdapter,
- shadeInteractor,
- avalancheController
- );
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
- }
- }
-
- private HeadsUpManagerPhone createHeadsUpManagerPhone() {
- return new TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- mStatusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- );
- }
-
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
- }
-
- public HeadsUpManagerPhoneTest(FlagsParameterization flags) {
- super(flags);
- }
-
- @Before
- public void setUp() {
- when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
- final AccessibilityManagerWrapper accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
- .thenReturn(TEST_AUTO_DISMISS_TIME);
- when(mVSProvider.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.ambient_notification_extension_time, 500);
-
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger, mBgHandler);
- }
-
- @Test
- public void testSnooze() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.snooze();
-
- assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
- }
-
- @Test
- public void testSwipedOutNotification() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.addSwipedOutNotification(entry.getKey());
-
- // Remove should succeed because the notification is swiped out
- final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
- /* releaseImmediately = */ false);
-
- assertTrue(removedImmediately);
- assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
- }
-
- @Test
- public void testCanRemoveImmediately_swipedOut() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.addSwipedOutNotification(entry.getKey());
-
- // Notification is swiped so it can be immediately removed.
- assertTrue(hmp.canRemoveImmediately(entry.getKey()));
- }
-
- @Ignore("b/141538055")
- @Test
- public void testCanRemoveImmediately_notTopEntry() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry earlierEntry =
- HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- final NotificationEntry laterEntry =
- HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext);
- laterEntry.setRow(mRow);
-
- hmp.showNotification(earlierEntry);
- hmp.showNotification(laterEntry);
-
- // Notification is "behind" a higher priority notification so we can remove it immediately.
- assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
- }
-
- @Test
- public void testExtendHeadsUp() {
- final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.extendHeadsUp();
- mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
-
- assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
new file mode 100644
index 0000000..d0ddbff
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -0,0 +1,439 @@
+/*
+ * 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.systemui.statusbar.policy
+
+import android.content.Context
+import android.os.Handler
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManagerTest(flags) {
+
+ private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ @Mock private lateinit var mGroupManager: GroupMembershipManager
+
+ @Mock private lateinit var mVSProvider: VisualStabilityProvider
+
+ @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+
+ @Mock private lateinit var mBypassController: KeyguardBypassController
+
+ @Mock private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Mock private lateinit var mAccessibilityManagerWrapper: AccessibilityManagerWrapper
+
+ @Mock private lateinit var mUiEventLogger: UiEventLogger
+
+ private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
+
+ @Mock private lateinit var mShadeInteractor: ShadeInteractor
+
+ @Mock private lateinit var dumpManager: DumpManager
+ private lateinit var mAvalancheController: AvalancheController
+
+ @Mock private lateinit var mBgHandler: Handler
+
+ private class TestableHeadsUpManagerPhone(
+ context: Context,
+ headsUpManagerLogger: HeadsUpManagerLogger,
+ groupManager: GroupMembershipManager,
+ visualStabilityProvider: VisualStabilityProvider,
+ statusBarStateController: StatusBarStateController,
+ keyguardBypassController: KeyguardBypassController,
+ configurationController: ConfigurationController,
+ globalSettings: GlobalSettings,
+ systemClock: SystemClock,
+ executor: DelayableExecutor,
+ accessibilityManagerWrapper: AccessibilityManagerWrapper,
+ uiEventLogger: UiEventLogger,
+ javaAdapter: JavaAdapter,
+ shadeInteractor: ShadeInteractor,
+ avalancheController: AvalancheController
+ ) :
+ HeadsUpManagerPhone(
+ context,
+ headsUpManagerLogger,
+ statusBarStateController,
+ keyguardBypassController,
+ groupManager,
+ visualStabilityProvider,
+ configurationController,
+ mockExecutorHandler(executor),
+ globalSettings,
+ systemClock,
+ executor,
+ accessibilityManagerWrapper,
+ uiEventLogger,
+ javaAdapter,
+ shadeInteractor,
+ avalancheController
+ ) {
+ init {
+ mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
+ mAutoDismissTime = TEST_AUTO_DISMISS_TIME
+ }
+
+ /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
+ fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
+ return shouldHeadsUpBecomePinned(entry)
+ }
+ }
+
+ private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
+ return TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ mStatusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ }
+
+ @Before
+ fun setUp() {
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
+ whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
+ val accessibilityMgr =
+ mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
+ whenever(
+ accessibilityMgr.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .thenReturn(TEST_AUTO_DISMISS_TIME)
+ mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
+ mContext
+ .getOrCreateTestableResources()
+ .addOverride(R.integer.ambient_notification_extension_time, 500)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ }
+
+ @Test
+ fun testSnooze() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.snooze()
+ Assert.assertTrue(hmp.isSnoozed(entry.sbn.packageName))
+ }
+
+ @Test
+ fun testSwipedOutNotification() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.addSwipedOutNotification(entry.key)
+
+ // Remove should succeed because the notification is swiped out
+ val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false)
+ Assert.assertTrue(removedImmediately)
+ Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
+ }
+
+ @Test
+ fun testCanRemoveImmediately_swipedOut() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.addSwipedOutNotification(entry.key)
+
+ // Notification is swiped so it can be immediately removed.
+ Assert.assertTrue(hmp.canRemoveImmediately(entry.key))
+ }
+
+ @Ignore("b/141538055")
+ @Test
+ fun testCanRemoveImmediately_notTopEntry() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
+ laterEntry.row = mRow
+ hmp.showNotification(earlierEntry)
+ hmp.showNotification(laterEntry)
+
+ // Notification is "behind" a higher priority notification so we can remove it immediately.
+ Assert.assertTrue(hmp.canRemoveImmediately(earlierEntry.key))
+ }
+
+ @Test
+ fun testExtendHeadsUp() {
+ val hmp = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.extendHeadsUp()
+ mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
+ Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(1207)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(true)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(false)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ companion object {
+ @get:Parameters(name = "{0}")
+ val flags: List<FlagsParameterization>
+ get() = buildList {
+ addAll(FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME))
+ addAll(
+ FlagsParameterization.allCombinationsOf(NotificationsHeadsUpRefactor.FLAG_NAME)
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
index 9e5db73..cd86402 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
@@ -26,6 +26,7 @@
@DependsOn(target = Callback.class)
public interface VolumeDialog extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_VOLUME";
+ String ACTION_VOLUME_UNDO = "com.android.systemui.volume.ACTION_VOLUME_UNDO";
int VERSION = 1;
void init(int windowType, Callback callback);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index c7998f0..4812ff0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -50,8 +50,8 @@
/** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
- /** A static thumbnail for rendering in some examples */
- fun getClockThumbnail(id: ClockId): Drawable?
+ /** Settings configuration parameters for the clock */
+ fun getClockPickerConfig(id: ClockId): ClockPickerConfig
}
/** Interface for controlling an active clock */
@@ -133,6 +133,7 @@
// both small and large clock should have a container (RelativeLayout in
// SimpleClockFaceController)
override val views = listOf(view)
+
override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
if (views.size != 1) {
throw IllegalArgumentException(
@@ -267,6 +268,25 @@
val clockId: ClockId,
)
+data class ClockPickerConfig(
+ val id: String,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /* Static & lightweight thumbnail version of the clock */
+ val thumbnail: Drawable,
+
+ /** True if the clock will react to tone changes in the seed color */
+ val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is capable of chagning style in reaction to touches */
+ val isReactiveToTouch: Boolean = false,
+)
+
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
val id: String,
@@ -280,7 +300,7 @@
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
- /** True if the clock will react to tone changes in the seed color. */
+ @Deprecated("TODO(b/352049256): Remove")
val isReactiveToTone: Boolean = true,
/** True if the clock is large frame clock, which will use weather in compose. */
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index b09d35d..5191895 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -60,6 +60,7 @@
android:layout_marginStart="16dp"
android:checked="true"
android:text="@string/backlinks_include_link"
+ android:textColor="?android:textColorSecondary"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/preview"
app:layout_constraintStart_toEndOf="@id/cancel"
@@ -74,6 +75,7 @@
android:drawablePadding="4dp"
android:gravity="center"
android:paddingHorizontal="8dp"
+ android:textColor="?android:textColorSecondary"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/preview"
app:layout_constraintStart_toEndOf="@id/backlinks_include_data"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 2cfd644..45a4af9 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -85,7 +85,7 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_gravity="center_vertical|start"
android:orientation="horizontal">
<Button
android:id="@+id/shortcut_system"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
deleted file mode 100644
index c134c8e..0000000
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2011 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.
- -->
-<com.android.systemui.screenshot.ScreenshotView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/screenshot_frame"
- android:theme="@style/FloatingOverlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="no">
- <ImageView
- android:id="@+id/screenshot_scrolling_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:clickable="true"
- android:importantForAccessibility="no"/>
- <ImageView
- android:id="@+id/screenshot_flash"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:elevation="7dp"
- android:src="@android:color/white"/>
- <include layout="@layout/screenshot_static"
- android:id="@+id/screenshot_static"/>
-</com.android.systemui.screenshot.ScreenshotView>
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 12e226a..afd4fa7 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -33,9 +33,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:singleLine="true"
- android:scrollHorizontally="true"
- android:ellipsize="marquee"
+ android:layout_marginEnd="@dimen/magnification_setting_view_item_horizontal_spacing"
android:text="@string/accessibility_magnifier_size"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:focusable="true"
@@ -120,9 +118,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:singleLine="true"
- android:scrollHorizontally="true"
- android:ellipsize="marquee"
+ android:layout_marginEnd="@dimen/magnification_setting_view_item_horizontal_spacing"
android:text="@string/accessibility_allow_diagonal_scrolling"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:labelFor="@id/magnifier_horizontal_lock_switch"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ca55c23..0350cd7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -100,8 +100,8 @@
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
- <color name="white">@*android:color/white</color>
- <color name="black">@*android:color/black</color>
+ <color name="navigation_bar_home_handle_light_color">#EBffffff</color>
+ <color name="navigation_bar_home_handle_dark_color">#99000000</color>
<!-- The shadow color for light navigation bar icons. -->
<color name="nav_key_button_shadow_color">#30000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 84d5dcb..30f23bf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -56,7 +56,7 @@
enabled for OLED devices to reduce/prevent burn in on the navigation bar (because of the
black background and static button placements) and disabled for all other devices to
prevent wasting cpu cycles on the dimming animation -->
- <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">false</bool>
+ <bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">4</integer>
@@ -104,7 +104,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
+ internet,bt,flashlight,dnd,modes,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
</string>
<!-- The tiles to display in QuickSettings -->
@@ -287,7 +287,8 @@
<integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
<!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
- to brightness values; -1 means keeping the current brightness. -->
+ to brightness values in the integer scale [1, 255]; -1 means keeping the current
+ brightness. -->
<integer-array name="config_doze_brightness_sensor_to_brightness">
<item>-1</item> <!-- 0: OFF -->
<item>2</item> <!-- 1: NIGHT -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 40bdc3e..eda7bb0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1323,6 +1323,7 @@
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
<!-- Magnification settings panel -->
<dimen name="magnification_setting_view_margin">24dp</dimen>
+ <dimen name="magnification_setting_view_item_horizontal_spacing">12dp</dimen>
<dimen name="magnification_setting_text_size">18sp</dimen>
<dimen name="magnification_setting_background_padding">24dp</dimen>
<dimen name="magnification_setting_background_corner_radius">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dee5528..2bd97d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -716,6 +716,8 @@
<!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
+ <!-- QuickSettings: Priority modes [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_modes_label">Priority modes</string>
<!-- QuickSettings: Bluetooth [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_label">Bluetooth</string>
<!-- QuickSettings: Bluetooth (Multiple) [CHAR LIMIT=NONE] -->
@@ -1871,7 +1873,7 @@
<string name="notification_channel_summary_low">No sound or vibration</string>
<!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
+ <string name="notification_conversation_summary_low">No sound or vibration but still appears in the conversation section</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
<string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
@@ -2050,6 +2052,8 @@
<string name="keyboard_key_page_down">Page Down</string>
<!-- Name used to refer to the "Delete" key on the keyboard. -->
<string name="keyboard_key_forward_del">Delete</string>
+ <!-- Name used to refer to the "Esc" or "Escape" key on the keyboard. -->
+ <string name="keyboard_key_esc">Esc</string>
<!-- Name used to refer to the "Home" move key on the keyboard. -->
<string name="keyboard_key_move_home">Home</string>
<!-- Name used to refer to the "End" move key on the keyboard. -->
@@ -3595,6 +3599,10 @@
that shows the user which keyboard shortcuts they can use. The "App shortcuts" are
for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_category_app_shortcuts">App shortcuts</string>
+ <!-- Default Title of the keyboard shortcut helper category for current app. The helper is a
+ component that shows the user which keyboard shortcuts they can use. The current app
+ shortcuts are shortcuts provided by the currently open app. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_current_app_shortcuts">Current App</string>
<!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component
that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts
are for example "Turn on talkback". [CHAR LIMIT=NONE] -->
@@ -3650,4 +3658,6 @@
<string name="home_controls_dream_label">Home Controls</string>
<!-- Description for home control panel [CHAR LIMIT=67] -->
<string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
+ <!-- Label for volume undo action [CHAR LIMIT=NONE] -->
+ <string name="volume_undo_action">Undo</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 047578c..36912ac 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -622,14 +622,14 @@
<style name="DualToneLightTheme">
<item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
- <item name="singleToneColor">@color/white</item>
- <item name="homeHandleColor">@color/white</item>
+ <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
+ <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
</style>
<style name="DualToneDarkTheme">
<item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
- <item name="singleToneColor">@color/black</item>
- <item name="homeHandleColor">@color/black</item>
+ <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
+ <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
</style>
<style name="QSHeaderDarkTheme">
<item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
@@ -648,7 +648,7 @@
<item name="singleToneColor">?android:attr/textColorPrimary</item>
</style>
<style name="ScreenPinningRequestTheme" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent">
- <item name="singleToneColor">@color/white</item>
+ <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
</style>
<style name="TextAppearance.Volume">
@@ -1604,7 +1604,6 @@
<item name="android:fontFamily">google-sans</item>
<item name="android:textColor">?androidprv:attr/textColorPrimary</item>
<item name="android:textSize">@dimen/magnification_setting_text_size</item>
- <item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.MagnificationSetting.EditButton">
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index ad09b46..c702927 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -85,6 +85,16 @@
<item>On</item>
</string-array>
+ <!-- State names for modes (Priority modes) tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_modes">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
+
<!-- State names for flashlight tile: unavailable, off, on.
This subtitle is shown when the tile is in that particular state but does not set its own
subtitle, so some of these may never appear on screen. They should still be translated as
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index fe61c46..eb0aae9 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -33,6 +33,7 @@
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="8dp"
+ app:layout_constraintBaseline_toBaselineOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
new file mode 100644
index 0000000..f10d92a
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
@@ -0,0 +1,81 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 090033d..5d804cc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -120,7 +120,7 @@
oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
/**
- * Notifies SystemUI to invoke IME Switcher.
+ * Notifies that the IME switcher button has been pressed.
*/
oneway void onImeSwitcherPressed() = 49;
@@ -167,5 +167,10 @@
*/
oneway void toggleQuickSettingsPanel() = 56;
- // Next id = 57
+ /**
+ * Notifies that the IME Switcher button has been long pressed.
+ */
+ oneway void onImeSwitcherLongPress() = 57;
+
+ // Next id = 58
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..484e758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,8 +124,6 @@
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
- // PiP animation is running
- public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
@@ -177,7 +175,6 @@
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
- SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -283,9 +280,6 @@
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
- if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
- str.add("disable_gesture_pip_animating");
- }
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a42f4c2d..baf8f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -54,7 +54,6 @@
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
import dagger.Lazy;
@@ -129,7 +128,6 @@
@Nullable
@Inject Lazy<VolumeDialogController> mVolumeDialogController;
@Inject Lazy<MetricsLogger> mMetricsLogger;
- @Inject Lazy<TunablePaddingService> mTunablePaddingService;
@Inject Lazy<UiOffloadThread> mUiOffloadThread;
@Inject Lazy<LightBarController> mLightBarController;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
@@ -177,7 +175,6 @@
mProviders.put(FragmentService.class, mFragmentService::get);
mProviders.put(VolumeDialogController.class, mVolumeDialogController::get);
mProviders.put(MetricsLogger.class, mMetricsLogger::get);
- mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index b4530ac..ed7062b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -26,6 +26,7 @@
import android.util.Range;
import android.view.WindowManager;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.util.settings.SecureSettings;
@@ -40,7 +41,8 @@
public class MagnificationSettingsController implements ComponentCallbacks {
// It should be consistent with the value defined in WindowMagnificationGestureHandler.
- private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(1.0f, 8.0f);
+ private static final Range<Float> A11Y_ACTION_SCALE_RANGE =
+ new Range<>(1.0f, MagnificationConstants.SCALE_MAX_VALUE);
private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index f2a68a8..5f6f21a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -55,7 +55,6 @@
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
-import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -90,7 +89,6 @@
private SeekBarWithIconButtonsView mZoomSeekbar;
private LinearLayout mAllowDiagonalScrollingView;
- private TextView mAllowDiagonalScrollingTitle;
private Switch mAllowDiagonalScrollingSwitch;
private LinearLayout mPanelView;
private LinearLayout mSettingView;
@@ -98,7 +96,6 @@
private ImageButton mMediumButton;
private ImageButton mLargeButton;
private Button mDoneButton;
- private TextView mSizeTitle;
private Button mEditButton;
private ImageButton mFullScreenButton;
private int mLastSelectedButtonIndex = MagnificationSize.NONE;
@@ -522,11 +519,8 @@
mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
- mSizeTitle = mSettingView.findViewById(R.id.magnifier_size_title);
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button);
- mAllowDiagonalScrollingTitle =
- mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
@@ -550,8 +544,6 @@
mDoneButton.setOnClickListener(mButtonClickListener);
mFullScreenButton.setOnClickListener(mButtonClickListener);
mEditButton.setOnClickListener(mButtonClickListener);
- mSizeTitle.setSelected(true);
- mAllowDiagonalScrollingTitle.setSelected(true);
mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
// Adds a pending post check to avoiding redundant calculation because this callback
@@ -578,6 +570,7 @@
// CONFIG_FONT_SCALE: font size change
// CONFIG_LOCALE: language change
// CONFIG_DENSITY: display size change
+ mParams.width = getPanelWidth(mContext);
mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
boolean showSettingPanelAfterConfigChange = mIsVisible;
@@ -660,9 +653,22 @@
mCallback.onEditMagnifierSizeMode(enable);
}
- private static LayoutParams createLayoutParams(Context context) {
+ private int getPanelWidth(Context context) {
+ // The magnification settings panel width is limited to the minimum of
+ // 1. display width
+ // 2. panel done button width + left and right padding
+ // So we can directly calculate the proper panel width at runtime
+ int displayWidth = mWindowManager.getCurrentWindowMetrics().getBounds().width();
+ int contentWidth = context.getResources()
+ .getDimensionPixelSize(R.dimen.magnification_setting_button_done_width);
+ int padding = context.getResources()
+ .getDimensionPixelSize(R.dimen.magnification_setting_background_padding);
+ return Math.min(displayWidth, contentWidth + 2 * padding);
+ }
+
+ private LayoutParams createLayoutParams(Context context) {
final LayoutParams params = new LayoutParams(
- LayoutParams.WRAP_CONTENT,
+ getPanelWidth(context),
LayoutParams.WRAP_CONTENT,
LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
LayoutParams.FLAG_NOT_FOCUSABLE,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 4a28d8b..27ded74 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -515,7 +515,7 @@
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
if (!activities.isEmpty()) {
- mContext.startActivity(intent);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
mStatusBarManager.collapsePanels();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 961d6aa..f041f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -186,7 +186,7 @@
}
@Override
- public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
dismissDialogIfExists();
Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
Bundle bundle = new Bundle();
@@ -198,7 +198,7 @@
}
@Override
- public void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ public void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice();
switch (deviceItem.getType()) {
case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE ->
@@ -226,8 +226,8 @@
final int activePresetIndex = mPresetsController.getActivePresetIndex();
refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setVisibility(
- (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE
- : GONE);
+ (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
+ && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
});
}
@@ -303,6 +303,11 @@
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
+ @VisibleForTesting
+ void setHearingDevicesPresetsController(HearingDevicesPresetsController controller) {
+ mPresetsController = controller;
+ }
+
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
mHearingDeviceItemList = getHearingDevicesList();
@@ -311,12 +316,15 @@
}
private void setupPresetSpinner(SystemUIDialog dialog) {
- mPresetsController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ if (mPresetsController == null) {
+ mPresetsController = new HearingDevicesPresetsController(mProfileManager,
+ mPresetCallback);
+ }
final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
mHearingDeviceItemList);
mPresetsController.setActiveHearingDevice(activeHearingDevice);
- mPresetInfoAdapter = new ArrayAdapter<String>(dialog.getContext(),
+ mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
R.layout.hearing_devices_preset_spinner_selected,
R.id.hearing_devices_preset_option_text);
mPresetInfoAdapter.setDropDownViewResource(
@@ -350,7 +358,8 @@
final int activePresetIndex = mPresetsController.getActivePresetIndex();
refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setVisibility(
- (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+ (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
+ && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
}
private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 56273eb..6e25744 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -33,6 +33,7 @@
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.assist.AssistLogger;
@@ -66,7 +67,7 @@
protected InvocationLightsView mInvocationLightsView;
protected final AssistLogger mAssistLogger;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final MetricsLogger mMetricsLogger;
private final Lazy<AssistManager> mAssistManagerLazy;
private final WindowManager.LayoutParams mLayoutParams;
@@ -80,12 +81,12 @@
@Inject
public DefaultUiController(Context context, AssistLogger assistLogger,
- WindowManager windowManager, MetricsLogger metricsLogger,
- Lazy<AssistManager> assistManagerLazy,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy,
NavigationBarController navigationBarController) {
mAssistLogger = assistLogger;
mRoot = new FrameLayout(context);
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mMetricsLogger = metricsLogger;
mAssistManagerLazy = assistManagerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index a9f985f..468737d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -288,6 +288,7 @@
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
withContext(backgroundDispatcher) {
if (isSuccessful) {
+ lockPatternUtils.userPresent(selectedUserId)
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
_hasLockoutOccurred.value = false
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 1d11dfb..4984fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -102,11 +102,7 @@
if (alternateBouncerSupported) {
combine(
keyguardTransitionInteractor.get().currentKeyguardState,
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.get().currentScene
- } else {
- flowOf(Scenes.Lockscreen)
- },
+ sceneInteractor.get().currentScene,
::Pair
)
.flatMapLatest { (currentKeyguardState, transitionState) ->
@@ -220,6 +216,7 @@
return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
}
+
/**
* Should only be called through StatusBarKeyguardViewManager which propagates the source of
* truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 6cb9b16..810b6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -32,7 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
@@ -75,7 +75,7 @@
private val clock: SystemClock,
private val biometricMessageInteractor: BiometricMessageInteractor,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
) {
@@ -119,7 +119,7 @@
}
} else if (authMethod.isSecure) {
combine(
- deviceEntryInteractor.deviceEntryRestrictionReason,
+ deviceUnlockedInteractor.deviceEntryRestrictionReason,
lockoutMessage,
fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
resetToDefault,
@@ -413,7 +413,7 @@
clock: SystemClock,
biometricMessageInteractor: BiometricMessageInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
+ deviceUnlockedInteractor: DeviceUnlockedInteractor,
fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
@@ -427,7 +427,7 @@
clock = clock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = faceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = fingerprintInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
index cdeeb6f..7abad14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
@@ -21,6 +21,9 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -29,6 +32,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@SysUISingleton
@@ -38,10 +42,15 @@
private val broadcastDispatcher: BroadcastDispatcher,
private val communalInteractor: CommunalInteractor,
@CommunalLog logBuffer: LogBuffer,
+ private val secureSettings: SecureSettings,
+ handler: Handler,
) : CoreStartable, BroadcastReceiver() {
private val logger = Logger(logBuffer, TAG)
+ private var oldToNewWidgetIdMap = emptyMap<Int, Int>()
+ private var userSetupComplete = false
+
override fun start() {
broadcastDispatcher.registerReceiver(
receiver = this,
@@ -73,8 +82,53 @@
return
}
- val oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
- communalInteractor.restoreWidgets(oldToNewWidgetIdMap)
+ oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
+
+ logger.i({ "On old to new widget ids mapping updated: $str1" }) {
+ str1 = oldToNewWidgetIdMap.toString()
+ }
+
+ maybeRestoreWidgets()
+
+ // Start observing if user setup is not complete
+ if (!userSetupComplete) {
+ startObservingUserSetupComplete()
+ }
+ }
+
+ private val userSetupObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ maybeRestoreWidgets()
+
+ // Stop observing once user setup is complete
+ if (userSetupComplete) {
+ stopObservingUserSetupComplete()
+ }
+ }
+ }
+
+ private fun maybeRestoreWidgets() {
+ val newValue = secureSettings.getInt(USER_SETUP_COMPLETE) > 0
+
+ if (userSetupComplete != newValue) {
+ userSetupComplete = newValue
+ logger.i({ "User setup complete: $bool1" }) { bool1 = userSetupComplete }
+ }
+
+ if (userSetupComplete && oldToNewWidgetIdMap.isNotEmpty()) {
+ logger.i("Starting to restore widgets")
+ communalInteractor.restoreWidgets(oldToNewWidgetIdMap.toMap())
+ oldToNewWidgetIdMap = emptyMap()
+ }
+ }
+
+ private fun startObservingUserSetupComplete() {
+ secureSettings.registerContentObserverSync(USER_SETUP_COMPLETE, userSetupObserver)
+ }
+
+ private fun stopObservingUserSetupComplete() {
+ secureSettings.unregisterContentObserverSync(userSetupObserver)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
new file mode 100644
index 0000000..78016c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.communal
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class CommunalOngoingContentStartable
+@Inject
+constructor(
+ @Background val bgScope: CoroutineScope,
+ private val communalInteractor: CommunalInteractor,
+ private val communalMediaRepository: CommunalMediaRepository,
+ private val communalSmartspaceRepository: CommunalSmartspaceRepository,
+ private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+ override fun start() {
+ if (
+ !featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) ||
+ !com.android.systemui.Flags.communalHub()
+ ) {
+ return
+ }
+
+ bgScope.launch {
+ communalInteractor.isCommunalEnabled.collect { enabled ->
+ if (enabled) {
+ communalMediaRepository.startListening()
+ communalSmartspaceRepository.startListening()
+ } else {
+ communalMediaRepository.stopListening()
+ communalSmartspaceRepository.stopListening()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 88c3f9f6..6b58c07 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,7 +18,9 @@
import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -28,6 +30,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,6 +78,7 @@
private val systemSettings: SystemSettings,
centralSurfacesOpt: Optional<CentralSurfaces>,
private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val featureFlagsClassic: FeatureFlagsClassic,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -86,13 +91,27 @@
private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt
+ private val flagEnabled: Boolean by lazy {
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ }
+
override fun start() {
+ if (!flagEnabled) {
+ return
+ }
+
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
- .onEach { nextScene ->
- communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
+ .onEach { (nextScene, nextTransition) ->
+ if (!communalSceneInteractor.isLaunchingWidget.value) {
+ // 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.
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
+ }
}
.launchIn(applicationScope)
@@ -188,7 +207,7 @@
private suspend fun determineSceneAfterTransition(
lastStartedTransition: TransitionStep,
- ): SceneKey? {
+ ): Pair<SceneKey, TransitionKey>? {
val to = lastStartedTransition.to
val from = lastStartedTransition.from
val docked = dockManager.isDocked
@@ -201,22 +220,27 @@
// 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.
- CommunalScenes.Blank
+ 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.
- CommunalScenes.Communal
+ 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 ->
- CommunalScenes.Blank
+ 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)
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ }
+ from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
+ // Make sure the communal hub is showing (immediately, not fading in) when
+ // transitioning from dozing to hub.
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
}
else -> null
}
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 2406cc6..3d201a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -52,6 +53,8 @@
CommunalWidgetModule::class,
CommunalPrefsRepositoryModule::class,
CommunalSettingsRepositoryModule::class,
+ CommunalSmartspaceRepositoryModule::class,
+ CommunalStartableModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
new file mode 100644
index 0000000..74a2cd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.communal.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.CommunalBackupRestoreStartable
+import com.android.systemui.communal.CommunalDreamStartable
+import com.android.systemui.communal.CommunalOngoingContentStartable
+import com.android.systemui.communal.CommunalSceneStartable
+import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface CommunalStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalLoggerStartable::class)
+ fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalSceneStartable::class)
+ fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalDreamStartable::class)
+ fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalAppWidgetHostStartable::class)
+ fun bindCommunalAppWidgetHostStartable(impl: CommunalAppWidgetHostStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalBackupRestoreStartable::class)
+ fun bindCommunalBackupRestoreStartable(impl: CommunalBackupRestoreStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalOngoingContentStartable::class)
+ fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
index a8e5174..c3d2683 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
@@ -43,11 +43,13 @@
val widgetsFromDb = runBlocking { database.communalWidgetDao().getWidgets().first() }
val widgetsState = mutableListOf<CommunalHubState.CommunalWidgetItem>()
widgetsFromDb.keys.forEach { rankItem ->
+ val widget = widgetsFromDb[rankItem]!!
widgetsState.add(
CommunalHubState.CommunalWidgetItem().apply {
rank = rankItem.rank
- widgetId = widgetsFromDb[rankItem]!!.widgetId
- componentName = widgetsFromDb[rankItem]?.componentName
+ widgetId = widget.widgetId
+ componentName = widget.componentName
+ userSerialNumber = widget.userSerialNumber
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 3ce8109..dff6352 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -17,17 +17,21 @@
package com.android.systemui.communal.data.db
import android.content.Context
+import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 1)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
companion object {
+ private const val TAG = "CommunalDatabase"
private var instance: CommunalDatabase? = null
/**
@@ -51,7 +55,8 @@
context.resources.getString(R.string.config_communalDatabase)
)
.also { builder ->
- builder.fallbackToDestructiveMigration(dropAllTables = false)
+ builder.addMigrations(MIGRATION_1_2)
+ builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
.build()
@@ -64,5 +69,23 @@
fun setInstance(database: CommunalDatabase) {
instance = database
}
+
+ /**
+ * This migration adds a user_serial_number column and sets its default value as
+ * [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED]. Work profile widgets added before the
+ * migration still work as expected, but they would be backed up as personal.
+ */
+ @VisibleForTesting
+ val MIGRATION_1_2 =
+ object : Migration(1, 2) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 1 to 2")
+ db.execSQL(
+ "ALTER TABLE communal_widget_table " +
+ "ADD COLUMN user_serial_number INTEGER NOT NULL DEFAULT " +
+ "${CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED}"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index 0d5336a..e33aead 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -29,7 +29,28 @@
@ColumnInfo(name = "component_name") val componentName: String,
/** Reference the id of an item persisted in the glanceable hub */
@ColumnInfo(name = "item_id") val itemId: Long,
-)
+ /**
+ * A serial number of the user that the widget provider is associated with. For example, a work
+ * profile widget.
+ *
+ * A serial number may be different from its user id in that user ids may be recycled but serial
+ * numbers are unique until the device is wiped.
+ *
+ * Most commonly, this value would be 0 for the primary user, and 10 for the work profile.
+ */
+ @ColumnInfo(name = "user_serial_number", defaultValue = "$USER_SERIAL_NUMBER_UNDEFINED")
+ val userSerialNumber: Int,
+) {
+ companion object {
+ /**
+ * The user serial number associated with the widget is undefined.
+ *
+ * This should only happen for widgets migrated from V1 before user serial number was
+ * included in the schema.
+ */
+ const val USER_SERIAL_NUMBER_UNDEFINED = -1
+ }
+}
@Entity(tableName = "communal_item_rank_table")
data class CommunalItemRank(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index d174fd1..933a25a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.data.db
import android.content.ComponentName
+import android.os.UserManager
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
@@ -26,7 +27,7 @@
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -34,58 +35,96 @@
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/**
* Callback that will be invoked when the Room database is created. Then the database will be
* populated with pre-configured default widgets to be rendered in the glanceable hub.
*/
+@SysUISingleton
class DefaultWidgetPopulation
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val bgScope: CoroutineScope,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
@Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
@CommunalLog logBuffer: LogBuffer,
+ private val userManager: UserManager,
) : RoomDatabase.Callback() {
companion object {
private const val TAG = "DefaultWidgetPopulation"
}
+
private val logger = Logger(logBuffer, TAG)
+ /**
+ * Reason for skipping default widgets population. Do not skip if this value is
+ * [SkipReason.NONE].
+ */
+ private var skipReason = SkipReason.NONE
+
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
- applicationScope.launch {
- addDefaultWidgets()
- logger.i("Default widgets were populated in the database.")
- }
- }
- // Read default widgets from config.xml and populate the database.
- private suspend fun addDefaultWidgets() =
- withContext(bgDispatcher) {
+ if (skipReason != SkipReason.NONE) {
+ logger.i("Skipped populating default widgets. Reason: $skipReason")
+ return
+ }
+
+ bgScope.launch {
+ // Default widgets should be associated with the main user.
+ val user = userManager.mainUser
+
+ if (user == null) {
+ logger.w(
+ "Skipped populating default widgets. Reason: device does not have a main user"
+ )
+ return@launch
+ }
+
+ val userSerialNumber = userManager.getUserSerialNumber(user.identifier)
+
defaultWidgets.forEachIndexed { index, name ->
val provider = ComponentName.unflattenFromString(name)
provider?.let {
- val id = communalWidgetHost.allocateIdAndBindWidget(provider)
+ val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
id?.let {
communalWidgetDaoProvider
.get()
.addWidget(
widgetId = id,
- provider = provider,
- priority = defaultWidgets.size - index
+ componentName = name,
+ priority = defaultWidgets.size - index,
+ userSerialNumber = userSerialNumber,
)
}
}
}
+
+ logger.i("Populated default widgets in the database.")
}
+ }
+
+ /**
+ * Skip populating default widgets in the Glanceable Hub when the database is created. This has
+ * no effect if default widgets have been populated already.
+ *
+ * @param skipReason Reason for skipping the default widgets population.
+ */
+ fun skipDefaultWidgetsPopulation(skipReason: SkipReason) {
+ this.skipReason = skipReason
+ }
+
+ /** Reason for skipping default widgets population. */
+ enum class SkipReason {
+ /** Do not skip. */
+ NONE,
+ /** Widgets are restored from a backup. */
+ RESTORED_FROM_BACKUP,
+ }
}
@Dao
@@ -106,10 +145,16 @@
fun deleteItemRankById(itemId: Long)
@Query(
- "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
- "VALUES(:widgetId, :componentName, :itemId)"
+ "INSERT INTO communal_widget_table" +
+ "(widget_id, component_name, item_id, user_serial_number) " +
+ "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber)"
)
- fun insertWidget(widgetId: Int, componentName: String, itemId: Long): Long
+ fun insertWidget(
+ widgetId: Int,
+ componentName: String,
+ itemId: Long,
+ userSerialNumber: Int,
+ ): Long
@Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
fun insertItemRank(rank: Int): Long
@@ -132,28 +177,41 @@
}
@Transaction
- fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long {
+ fun addWidget(
+ widgetId: Int,
+ provider: ComponentName,
+ priority: Int,
+ userSerialNumber: Int,
+ ): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
@Transaction
- fun addWidget(widgetId: Int, componentName: String, priority: Int): Long {
+ fun addWidget(
+ widgetId: Int,
+ componentName: String,
+ priority: Int,
+ userSerialNumber: Int,
+ ): Long {
return insertWidget(
widgetId = widgetId,
componentName = componentName,
itemId = insertItemRank(priority),
+ userSerialNumber = userSerialNumber,
)
}
@Transaction
fun deleteWidgetById(widgetId: Int): Boolean {
val widget =
- getWidgetByIdNow(widgetId) ?: // no entry to delete from db
- return false
+ getWidgetByIdNow(widgetId)
+ ?: // no entry to delete from db
+ return false
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
@@ -166,6 +224,8 @@
clearCommunalWidgetsTable()
clearCommunalItemRankTable()
- state.widgets.forEach { addWidget(it.widgetId, it.componentName, it.rank) }
+ state.widgets.forEach {
+ addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt
new file mode 100644
index 0000000..ff9dddd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.model
+
+import android.widget.RemoteViews
+
+/** Data model of a smartspace timer in the Glanceable Hub. */
+data class CommunalSmartspaceTimer(
+ /** Unique id that identifies the timer. */
+ val smartspaceTargetId: String,
+ /** Timestamp in milliseconds of when the timer was created. */
+ val createdTimestampMillis: Long,
+ /** Remote views for the timer that is rendered in Glanceable Hub. */
+ val remoteViews: RemoteViews,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e5a0e50..fe9154c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -30,6 +30,12 @@
/** Encapsulates the state of smartspace in communal. */
interface CommunalMediaRepository {
val mediaModel: Flow<CommunalMediaModel>
+
+ /** Start listening for media updates. */
+ fun startListening()
+
+ /** Stop listening for media updates. */
+ fun stopListening()
}
@SysUISingleton
@@ -38,29 +44,7 @@
constructor(
private val mediaDataManager: MediaDataManager,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
-) : CommunalMediaRepository {
-
- private val mediaDataListener =
- object : MediaDataManager.Listener {
- override fun onMediaDataLoaded(
- key: String,
- oldKey: String?,
- data: MediaData,
- immediately: Boolean,
- receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
- ) {
- updateMediaModel(data)
- }
-
- override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
- updateMediaModel()
- }
- }
-
- init {
- mediaDataManager.addListener(mediaDataListener)
- }
+) : CommunalMediaRepository, MediaDataManager.Listener {
private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
MutableStateFlow(CommunalMediaModel.INACTIVE)
@@ -72,6 +56,29 @@
initialValue = CommunalMediaModel.INACTIVE,
)
+ override fun startListening() {
+ mediaDataManager.addListener(this)
+ }
+
+ override fun stopListening() {
+ mediaDataManager.removeListener(this)
+ }
+
+ override fun onMediaDataLoaded(
+ key: String,
+ oldKey: String?,
+ data: MediaData,
+ immediately: Boolean,
+ receivedSmartspaceCardLatency: Int,
+ isSsReactivated: Boolean
+ ) {
+ updateMediaModel(data)
+ }
+
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ updateMediaModel()
+ }
+
private fun updateMediaModel(data: MediaData? = null) {
if (mediaDataManager.hasActiveMediaOrRecommendation()) {
_mediaModel.value =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index e3ef6bb..748c4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -102,7 +103,10 @@
.broadcastFlow(
filter =
IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = user.userHandle
+ // In COPE management mode, the restriction from the managed profile may
+ // propagate to the main profile. Therefore listen to this broadcast across
+ // all users and update the state each time it changes.
+ user = UserHandle.ALL,
)
.emitOnStart()
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
new file mode 100644
index 0000000..e1d9bef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.communal.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import android.os.Parcelable
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.util.time.SystemClock
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+interface CommunalSmartspaceRepository {
+ /** Smartspace timer targets for the communal surface. */
+ val timers: Flow<List<CommunalSmartspaceTimer>>
+
+ /** Start listening for smartspace updates. */
+ fun startListening()
+
+ /** Stop listening for smartspace updates. */
+ fun stopListening()
+}
+
+@SysUISingleton
+class CommunalSmartspaceRepositoryImpl
+@Inject
+constructor(
+ private val communalSmartspaceController: CommunalSmartspaceController,
+ @Main private val uiExecutor: Executor,
+ private val systemClock: SystemClock,
+) : CommunalSmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+
+ private val _timers: MutableStateFlow<List<CommunalSmartspaceTimer>> =
+ MutableStateFlow(emptyList())
+ override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
+
+ private var targetCreationTimes = emptyMap<String, Long>()
+
+ override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
+ val targets = targetsNullable?.filterIsInstance<SmartspaceTarget>() ?: emptyList()
+ val timerTargets =
+ targets
+ .filter { target ->
+ target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+ target.remoteViews != null
+ }
+ .associateBy { stableId(it.smartspaceTargetId) }
+
+ // The creation times from smartspace targets are unreliable (b/318535930). Therefore,
+ // SystemUI uses the timestamp of which a timer first appears, and caches these values to
+ // prevent timers from swapping positions in the hub.
+ targetCreationTimes =
+ timerTargets.mapValues { (stableId, _) ->
+ targetCreationTimes[stableId] ?: systemClock.currentTimeMillis()
+ }
+
+ _timers.value =
+ timerTargets.map { (stableId, target) ->
+ CommunalSmartspaceTimer(
+ // The view layer should have the instance based smartspaceTargetId instead of
+ // stable id, so that when a new instance of the timer is created, for example,
+ // when it is paused, the view should re-render its remote views.
+ smartspaceTargetId = target.smartspaceTargetId,
+ createdTimestampMillis = targetCreationTimes[stableId]!!,
+ remoteViews = target.remoteViews!!,
+ )
+ }
+ }
+
+ override fun startListening() {
+ if (android.app.smartspace.flags.Flags.remoteViews()) {
+ uiExecutor.execute {
+ communalSmartspaceController.addListener(
+ listener = this@CommunalSmartspaceRepositoryImpl
+ )
+ }
+ }
+ }
+
+ override fun stopListening() {
+ uiExecutor.execute {
+ communalSmartspaceController.removeListener(
+ listener = this@CommunalSmartspaceRepositoryImpl
+ )
+ }
+ }
+
+ companion object {
+ /**
+ * The smartspace target id is instance-based, meaning a single timer (from the user's
+ * perspective) can have multiple instances. For example, when a timer is paused, a new
+ * instance is created. To address this, SystemUI manually removes the instance id to
+ * maintain a consistent id across sessions.
+ *
+ * It is assumed that timer target ids follow this format: timer-${stableId}-${instanceId}.
+ * This function returns timer-${stableId}, stripping out the instance id.
+ */
+ @VisibleForTesting
+ fun stableId(targetId: String): String {
+ return targetId.split("-").take(2).joinToString("-")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
index c92234c..b11c6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.communal.data.repository
-import javax.inject.Qualifier
+import dagger.Binds
+import dagger.Module
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Module
+interface CommunalSmartspaceRepositoryModule {
+ @Binds
+ fun communalSmartspaceRepository(
+ impl: CommunalSmartspaceRepositoryImpl
+ ): CommunalSmartspaceRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index fdb797d..e65e5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,10 +20,14 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
+import android.os.UserManager
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
+import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -98,6 +102,8 @@
private val backupManager: BackupManager,
private val backupUtils: CommunalBackupUtils,
packageChangeRepository: PackageChangeRepository,
+ private val userManager: UserManager,
+ private val defaultWidgetPopulation: DefaultWidgetPopulation,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -185,6 +191,7 @@
widgetId = id,
provider = provider,
priority = priority,
+ userSerialNumber = userManager.getUserSerialNumber(user.identifier),
)
backupManager.dataChanged()
} else {
@@ -228,9 +235,38 @@
return@launch
}
+ // Abort restoring widgets if this code is somehow run on a device that does not have
+ // a main user, e.g. auto.
+ val mainUser = userManager.mainUser
+ if (mainUser == null) {
+ logger.w("Skipped restoring widgets because device does not have a main user")
+ return@launch
+ }
+
val widgetsWithHost = appWidgetHost.appWidgetIds.toList()
val widgetsToRemove = widgetsWithHost.toMutableList()
+ val oldUserSerialNumbers = state.widgets.map { it.userSerialNumber }.distinct()
+ val usersMap =
+ oldUserSerialNumbers.associateWith { oldUserSerialNumber ->
+ if (oldUserSerialNumber == CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED) {
+ // If user serial number from the backup is undefined, the widget was added
+ // to the hub before user serial numbers are stored in the database. In this
+ // case, we restore the widget with the main user.
+ mainUser
+ } else {
+ // If the user serial number is defined, look up whether the user is
+ // restored. This API returns a user handle matching its backed up user
+ // serial number, if the user is restored. Otherwise, null is returned.
+ backupManager.getUserForAncestralSerialNumber(oldUserSerialNumber.toLong())
+ ?: null
+ }
+ }
+ logger.d({ "Restored users map: $str1" }) { str1 = usersMap.toString() }
+
+ // A set to hold all widgets that belong to non-main users
+ val secondaryUserWidgets = mutableSetOf<CommunalHubState.CommunalWidgetItem>()
+
// Produce a new state to be restored, skipping invalid widgets
val newWidgets =
state.widgets.mapNotNull { restoredWidget ->
@@ -249,20 +285,67 @@
return@mapNotNull null
}
+ // Skip if user / profile is not registered
+ val newUser = usersMap[restoredWidget.userSerialNumber]
+ if (newUser == null) {
+ logger.d({
+ "Skipped restoring widget $int1 because its user $int2 is not " +
+ "registered"
+ }) {
+ int1 = restoredWidget.widgetId
+ int2 = restoredWidget.userSerialNumber
+ }
+ return@mapNotNull null
+ }
+
+ // Place secondary user widgets in a bucket to be manually bound later because
+ // of a platform bug (b/349852237) that backs up work profile widgets as
+ // personal.
+ if (newUser.identifier != mainUser.identifier) {
+ logger.d({
+ "Skipped restoring widget $int1 for now because its new user $int2 " +
+ "is secondary. This widget will be bound later."
+ }) {
+ int1 = restoredWidget.widgetId
+ int2 = newUser.identifier
+ }
+ secondaryUserWidgets.add(restoredWidget)
+ return@mapNotNull null
+ }
+
widgetsToRemove.remove(newWidgetId)
CommunalHubState.CommunalWidgetItem().apply {
widgetId = newWidgetId
componentName = restoredWidget.componentName
rank = restoredWidget.rank
+ userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
}
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
+ // Skip default widgets population
+ defaultWidgetPopulation.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
// Restore database
- logger.i("Restoring communal database $newState")
+ logger.i("Restoring communal database:\n$newState")
communalWidgetDao.restoreCommunalHubState(newState)
+ // Manually bind each secondary user widget due to platform bug b/349852237
+ secondaryUserWidgets.forEach { widget ->
+ val newUser = usersMap[widget.userSerialNumber]!!
+ logger.i({ "Binding secondary user ($int1) widget $int2: $str1" }) {
+ int1 = newUser.identifier
+ int2 = widget.widgetId
+ str1 = widget.componentName
+ }
+ addWidget(
+ provider = ComponentName.unflattenFromString(widget.componentName)!!,
+ user = newUser,
+ priority = widget.rank,
+ )
+ }
+
// Delete restored state file from disk
backupUtils.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 86f5fe1..3fffd76 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.domain.interactor
-import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
@@ -29,6 +28,7 @@
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
@@ -60,7 +60,6 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
-import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
@@ -82,7 +81,6 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -101,7 +99,7 @@
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsInteractor: CommunalPrefsInteractor,
private val mediaRepository: CommunalMediaRepository,
- smartspaceRepository: SmartspaceRepository,
+ private val smartspaceRepository: CommunalSmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
communalSettingsInteractor: CommunalSettingsInteractor,
@@ -435,19 +433,6 @@
}
}
- /** A flow of available smartspace targets. Currently only showing timers. */
- private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
- if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
- flowOf(emptyList())
- } else {
- smartspaceRepository.communalSmartspaceTargets.map { targets ->
- targets.filter { target ->
- target.featureType == SmartspaceTarget.FEATURE_TIMER &&
- target.remoteViews != null
- }
- }
- }
-
/** CTA tile to be displayed in the glanceable hub (view mode). */
val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
communalPrefsInteractor.isCtaDismissed.map { isDismissed ->
@@ -472,16 +457,16 @@
* sized dynamically.
*/
fun getOngoingContent(mediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
- combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+ combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
- // Add smartspace
+ // Add smartspace timers
ongoingContent.addAll(
- smartspace.map { target ->
+ timers.map { timer ->
CommunalContentModel.Smartspace(
- smartspaceTargetId = target.smartspaceTargetId,
- remoteViews = target.remoteViews!!,
- createdTimestampMillis = target.creationTimeMillis,
+ smartspaceTargetId = timer.smartspaceTargetId,
+ remoteViews = timer.remoteViews,
+ createdTimestampMillis = timer.createdTimestampMillis,
)
}
)
@@ -556,4 +541,29 @@
)
}
}
+
+ /**
+ * {@link #setScrollPosition} persists the current communal grid scroll position (to volatile
+ * memory) so that the next presentation of the grid (either as glanceable hub or edit mode) can
+ * restore position.
+ */
+ fun setScrollPosition(firstVisibleItemIndex: Int, firstVisibleItemOffset: Int) {
+ _firstVisibleItemIndex = firstVisibleItemIndex
+ _firstVisibleItemOffset = firstVisibleItemOffset
+ }
+
+ fun resetScrollPosition() {
+ _firstVisibleItemIndex = 0
+ _firstVisibleItemOffset = 0
+ }
+
+ val firstVisibleItemIndex: Int
+ get() = _firstVisibleItemIndex
+
+ private var _firstVisibleItemIndex: Int = 0
+
+ val firstVisibleItemOffset: Int
+ get() = _firstVisibleItemOffset
+
+ private var _firstVisibleItemOffset: Int = 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index fd540c4..122f9647 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -48,6 +48,15 @@
@Application private val applicationScope: CoroutineScope,
private val communalSceneRepository: CommunalSceneRepository,
) {
+ val _isLaunchingWidget = MutableStateFlow(false)
+
+ /** Whether a widget launch is currently in progress. */
+ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
+
+ fun setIsLaunchingWidget(launching: Boolean) {
+ _isLaunchingWidget.value = launching
+ }
+
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index 0816259..bc14ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -35,5 +35,8 @@
// Rank or order of the widget in the communal hub.
int32 rank = 3;
+
+ // Serial number of the user associated with the widget.
+ int32 user_serial_number = 4;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index a88b777..4e3d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -22,21 +22,25 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
-/**
- * Handles interactions on smartspace elements on the hub.
- */
-class SmartspaceInteractionHandler @Inject constructor(
+/** Handles interactions on smartspace elements on the hub. */
+class SmartspaceInteractionHandler
+@Inject
+constructor(
private val activityStarter: ActivityStarter,
+ communalSceneInteractor: CommunalSceneInteractor,
) : RemoteViews.InteractionHandler {
- private val delegate = InteractionHandlerDelegate(
- findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
- intentStarter = this::startIntent,
- )
+ private val delegate =
+ InteractionHandlerDelegate(
+ communalSceneInteractor,
+ findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
override fun onInteraction(
view: View,
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 6ec6ec1..19d7ceb 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
@@ -59,6 +59,18 @@
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+ /**
+ * The up-to-date value of the grid scroll offset. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollOffset = 0
+
+ /**
+ * The up-to-date value of the grid scroll index. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollIndex = 0
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
@@ -147,6 +159,28 @@
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called when the grid scroll position has been updated. */
+ open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) {
+ currentScrollIndex = firstVisibleItemIndex
+ currentScrollOffset = firstVisibleItemScroll
+ }
+
+ /** Stores scroll values to interactor. */
+ protected fun persistScrollPosition() {
+ communalInteractor.setScrollPosition(currentScrollIndex, currentScrollOffset)
+ }
+
+ /** Invoked after scroll values are used to initialize grid position. */
+ open fun clearPersistedScrollPosition() {
+ communalInteractor.setScrollPosition(0, 0)
+ }
+
+ val savedFirstScrollIndex: Int
+ get() = communalInteractor.firstVisibleItemIndex
+
+ val savedFirstScrollOffset: Int
+ get() = communalInteractor.firstVisibleItemOffset
+
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
_selectedKey.value = key
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 18b343e..7b0aadf 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
@@ -43,6 +43,7 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
@@ -93,7 +94,10 @@
*/
val canShowEditMode =
allOf(
- keyguardTransitionInteractor.isFinishedIn(KeyguardState.GONE),
+ keyguardTransitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE
+ ),
communalInteractor.editModeOpen
)
.filter { it }
@@ -216,6 +220,9 @@
/** Called when exiting the edit mode, before transitioning back to the communal scene. */
fun cleanupEditModeState() {
communalSceneInteractor.setEditModeState(null)
+
+ // Set the scroll position of the glanceable hub to match where we are now.
+ persistScrollPosition()
}
companion object {
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 780bf70..02ecfe1 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
@@ -233,7 +233,10 @@
override fun onOpenWidgetEditor(
shouldOpenWidgetPickerOnStart: Boolean,
- ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ ) {
+ persistScrollPosition()
+ communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ }
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index 40b182d..51a5fcd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -24,17 +24,17 @@
import androidx.core.util.component1
import androidx.core.util.component2
import com.android.systemui.animation.ActivityTransitionAnimator
-
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
/** A delegate that can be used to launch activities from [RemoteViews] */
class InteractionHandlerDelegate(
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val findViewToAnimate: (View) -> Boolean,
private val intentStarter: IntentStarter,
) : RemoteViews.InteractionHandler {
- /**
- * Responsible for starting the pending intent for launching activities.
- */
+ /** Responsible for starting the pending intent for launching activities. */
fun interface IntentStarter {
fun startPendingIntent(
intent: PendingIntent,
@@ -57,7 +57,10 @@
// activities.
val hostView = getNearestParent(view)
val animationController =
- hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+ hostView?.let(ActivityTransitionAnimator.Controller::fromView)?.let {
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ CommunalTransitionAnimatorController(it, communalSceneInteractor)
+ }
val (fillInIntent, activityOptions) = launchOptions
intentStarter.startPendingIntent(
pendingIntent,
@@ -66,7 +69,6 @@
animationController
)
}
-
else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
new file mode 100644
index 0000000..4efaf87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.communal.widgets
+
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+
+/**
+ * An [ActivityTransitionAnimator.Controller] that takes care of updating the state of the Communal
+ * Hub at the right time.
+ */
+class CommunalTransitionAnimatorController(
+ delegate: ActivityTransitionAnimator.Controller,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+) : DelegateTransitionAnimatorController(delegate) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ if (!willAnimate) {
+ // Other callbacks won't happen, so reset the state here.
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ }
+ delegate.onIntentStarted(willAnimate)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index 2000f96..684303ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -43,12 +43,6 @@
@SysUISingleton
@Provides
- fun provideAppWidgetManager(@Application context: Context): Optional<AppWidgetManager> {
- return Optional.ofNullable(AppWidgetManager.getInstance(context))
- }
-
- @SysUISingleton
- @Provides
fun provideCommunalAppWidgetHost(
@Application context: Context,
@Background backgroundScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 72f9180..519903e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -22,6 +22,7 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
@@ -32,10 +33,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
+ private val communalSceneInteractor: CommunalSceneInteractor
) : RemoteViews.InteractionHandler {
private val delegate =
InteractionHandlerDelegate(
+ communalSceneInteractor,
findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
intentStarter = this::startIntent,
)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b0fc60e..2ea27b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -16,6 +16,9 @@
package com.android.systemui.dagger;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -39,6 +42,7 @@
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
+import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.companion.virtual.VirtualDeviceManager;
@@ -111,6 +115,9 @@
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.app.NotificationManagerCompat;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
+import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.jank.InteractionJankMonitor;
@@ -125,6 +132,7 @@
import com.android.systemui.user.utils.UserScopedService;
import com.android.systemui.user.utils.UserScopedServiceImpl;
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -367,6 +375,12 @@
@Provides
@Singleton
+ static Optional<AppWidgetManager> provideAppWidgetManager(@Application Context context) {
+ return Optional.ofNullable(AppWidgetManager.getInstance(context));
+ }
+
+ @Provides
+ @Singleton
static IAppWidgetService provideIAppWidgetService() {
return IAppWidgetService.Stub.asInterface(
ServiceManager.getService(Context.APPWIDGET_SERVICE));
@@ -680,6 +694,15 @@
@Provides
@Singleton
+ static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
+ WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
+ return new ViewCaptureAwareWindowManager(windowManager,
+ /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
+ /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+ }
+
+ @Provides
+ @Singleton
static PermissionManager providePermissionManager(Context context) {
PermissionManager pm = context.getSystemService(PermissionManager.class);
if (pm != null) {
@@ -764,4 +787,10 @@
return IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
}
+
+ @Provides
+ @Singleton
+ static ViewCapture provideViewCapture(Context context) {
+ return ViewCaptureFactory.getInstance(context);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 593196c..88601da 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,11 +24,6 @@
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
-import com.android.systemui.communal.CommunalDreamStartable
-import com.android.systemui.communal.CommunalBackupRestoreStartable
-import com.android.systemui.communal.CommunalSceneStartable
-import com.android.systemui.communal.log.CommunalLoggerStartable
-import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -80,12 +75,13 @@
* @deprecated
*/
@Module(
- includes = [
- MultiUserUtilsModule::class,
- StartControlsStartableModule::class,
- StartBinderLoggerModule::class,
- WallpaperModule::class,
- ]
+ includes =
+ [
+ MultiUserUtilsModule::class,
+ StartControlsStartableModule::class,
+ StartBinderLoggerModule::class,
+ WallpaperModule::class,
+ ]
)
abstract class SystemUICoreStartableModule {
/** Inject into BiometricNotificationService */
@@ -96,25 +92,25 @@
service: BiometricNotificationService
): CoreStartable
- /** Inject into ClipboardListener. */
+ /** Inject into ClipboardListener. */
@Binds
@IntoMap
@ClassKey(ClipboardListener::class)
abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
- /** Inject into GlobalActionsComponent. */
+ /** Inject into GlobalActionsComponent. */
@Binds
@IntoMap
@ClassKey(GlobalActionsComponent::class)
abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
- /** Inject into InstantAppNotifier. */
+ /** Inject into InstantAppNotifier. */
@Binds
@IntoMap
@ClassKey(InstantAppNotifier::class)
abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable
- /** Inject into KeyboardUI. */
+ /** Inject into KeyboardUI. */
@Binds
@IntoMap
@ClassKey(KeyboardUI::class)
@@ -125,7 +121,7 @@
@IntoMap
@ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
abstract fun bindProjectedTaskListener(
- sysui: MediaProjectionTaskSwitcherCoreStartable
+ sysui: MediaProjectionTaskSwitcherCoreStartable
): CoreStartable
/** Inject into KeyguardBiometricLockoutLogger */
@@ -136,38 +132,38 @@
sysui: KeyguardBiometricLockoutLogger
): CoreStartable
- /** Inject into KeyguardViewMediator. */
+ /** Inject into KeyguardViewMediator. */
@Binds
@IntoMap
@ClassKey(KeyguardViewMediator::class)
abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable
- /** Inject into LatencyTests. */
+ /** Inject into LatencyTests. */
@Binds
@IntoMap
@ClassKey(LatencyTester::class)
abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
- /** Inject into DisplaySwitchLatencyTracker. */
+ /** Inject into DisplaySwitchLatencyTracker. */
@Binds
@IntoMap
@ClassKey(DisplaySwitchLatencyTracker::class)
abstract fun bindDisplaySwitchLatencyTracker(sysui: DisplaySwitchLatencyTracker): CoreStartable
- /** Inject into NotificationChannels. */
+ /** Inject into NotificationChannels. */
@Binds
@IntoMap
@ClassKey(NotificationChannels::class)
@PerUser
abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
- /** Inject into ImmersiveModeConfirmation. */
+ /** Inject into ImmersiveModeConfirmation. */
@Binds
@IntoMap
@ClassKey(ImmersiveModeConfirmation::class)
abstract fun bindImmersiveModeConfirmation(sysui: ImmersiveModeConfirmation): CoreStartable
- /** Inject into RingtonePlayer. */
+ /** Inject into RingtonePlayer. */
@Binds
@IntoMap
@ClassKey(RingtonePlayer::class)
@@ -179,50 +175,49 @@
@ClassKey(GesturePointerEventListener::class)
abstract fun bindGesturePointerEventListener(sysui: GesturePointerEventListener): CoreStartable
- /** Inject into SessionTracker. */
+ /** Inject into SessionTracker. */
@Binds
@IntoMap
@ClassKey(SessionTracker::class)
abstract fun bindSessionTracker(service: SessionTracker): CoreStartable
- /** Inject into ShortcutKeyDispatcher. */
+ /** Inject into ShortcutKeyDispatcher. */
@Binds
@IntoMap
@ClassKey(ShortcutKeyDispatcher::class)
abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable
- /** Inject into SliceBroadcastRelayHandler. */
+ /** Inject into SliceBroadcastRelayHandler. */
@Binds
@IntoMap
@ClassKey(SliceBroadcastRelayHandler::class)
abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable
- /** Inject into StorageNotification. */
+ /** Inject into StorageNotification. */
@Binds
@IntoMap
@ClassKey(StorageNotification::class)
abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
- /** Inject into ThemeOverlayController. */
+ /** Inject into ThemeOverlayController. */
@Binds
@IntoMap
@ClassKey(ThemeOverlayController::class)
abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
-
- /** Inject into MediaOutputSwitcherDialogUI. */
+ /** Inject into MediaOutputSwitcherDialogUI. */
@Binds
@IntoMap
@ClassKey(MediaOutputSwitcherDialogUI::class)
abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
- /** Inject into Magnification. */
+ /** Inject into Magnification. */
@Binds
@IntoMap
@ClassKey(Magnification::class)
abstract fun bindMagnification(sysui: Magnification): CoreStartable
- /** Inject into WMShell. */
+ /** Inject into WMShell. */
@Binds
@IntoMap
@ClassKey(WMShell::class)
@@ -239,7 +234,7 @@
@IntoMap
@ClassKey(MediaTttChipControllerReceiver::class)
abstract fun bindMediaTttChipControllerReceiver(
- sysui: MediaTttChipControllerReceiver
+ sysui: MediaTttChipControllerReceiver
): CoreStartable
/** Inject into MediaTttCommandLineHelper. */
@@ -254,8 +249,6 @@
@ClassKey(ChipbarCoordinator::class)
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
-
-
/** Inject into StylusUsiPowerStartable) */
@Binds
@IntoMap
@@ -267,21 +260,21 @@
@ClassKey(PhysicalKeyboardCoreStartable::class)
abstract fun bindKeyboardCoreStartable(listener: PhysicalKeyboardCoreStartable): CoreStartable
- /** Inject into MuteQuickAffordanceCoreStartable*/
+ /** Inject into MuteQuickAffordanceCoreStartable */
@Binds
@IntoMap
@ClassKey(MuteQuickAffordanceCoreStartable::class)
abstract fun bindMuteQuickAffordanceCoreStartable(
- sysui: MuteQuickAffordanceCoreStartable
+ sysui: MuteQuickAffordanceCoreStartable
): CoreStartable
- /**Inject into DreamMonitor */
+ /** Inject into DreamMonitor */
@Binds
@IntoMap
@ClassKey(DreamMonitor::class)
abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
- /**Inject into AssistantAttentionMonitor */
+ /** Inject into AssistantAttentionMonitor */
@Binds
@IntoMap
@ClassKey(AssistantAttentionMonitor::class)
@@ -321,35 +314,6 @@
@Binds
@IntoMap
- @ClassKey(CommunalLoggerStartable::class)
- abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(CommunalSceneStartable::class)
- abstract fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(CommunalDreamStartable::class)
- abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(CommunalAppWidgetHostStartable::class)
- abstract fun bindCommunalAppWidgetHostStartable(
- impl: CommunalAppWidgetHostStartable
- ): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(CommunalBackupRestoreStartable::class)
- abstract fun bindCommunalBackupRestoreStartable(
- impl: CommunalBackupRestoreStartable
- ): CoreStartable
-
- @Binds
- @IntoMap
@ClassKey(HomeControlsDreamStartable::class)
abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index ed30d59..fa52dad 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -303,7 +303,10 @@
private fun listenForSchedulingWatchdog() {
keyguardTransitionInteractor
- .transition(Edge.create(to = KeyguardState.GONE))
+ .transition(
+ edge = Edge.create(to = Scenes.Gone),
+ edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
+ )
.filter { it.transitionState == TransitionState.FINISHED }
.onEach {
// We deliberately want to run this in background because scheduleWatchdog does
@@ -324,7 +327,7 @@
combine(
keyguardTransitionInteractor.isFinishedIn(
scene = Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
keyguardInteractor.statusBarState,
) { isFinishedInGoneState, statusBarState ->
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 0f18978..e2ad774 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -39,12 +39,6 @@
* the lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean>
-
- /**
- * Reports, to system server, that the user is "present" now. This is a signal that system
- * server uses to know that the device has been entered.
- */
- suspend fun reportUserPresent()
}
/** Encapsulates application state for device entry. */
@@ -84,17 +78,6 @@
SharingStarted.Eagerly,
initialValue = keyguardBypassController.bypassEnabled,
)
-
- override suspend fun reportUserPresent() {
- withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUser.value.userInfo.id
- lockPatternUtils.userPresent(selectedUserId)
- }
- }
-
- companion object {
- private const val TAG = "DeviceEntryRepositoryImpl"
- }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 425bb96..ea0e59b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,33 +16,24 @@
package com.android.systemui.deviceentry.domain.interactor
-import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.Quad
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -61,12 +52,7 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
- private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
- private val trustInteractor: TrustInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
- private val systemPropertiesHelper: SystemPropertiesHelper,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
) {
/**
@@ -109,11 +95,6 @@
false
}
}
- .onEach { isDeviceEntered ->
- if (isDeviceEntered) {
- repository.reportUserPresent()
- }
- }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -156,70 +137,6 @@
initialValue = null,
)
- private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
- private val fingerprintEnrolledAndEnabled =
- biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
- private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
-
- private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
- combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
-
- /**
- * Reason why device entry is restricted to certain authentication methods for the current user.
- *
- * Emits null when there are no device entry restrictions active.
- */
- val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
- faceOrFingerprintOrTrustEnabled.flatMapLatest {
- (faceEnabled, fingerprintEnabled, trustEnabled) ->
- if (faceEnabled || fingerprintEnabled || trustEnabled) {
- combine(
- biometricSettingsInteractor.authenticationFlags,
- faceAuthInteractor.isLockedOut,
- fingerprintAuthInteractor.isLockedOut,
- trustInteractor.isTrustAgentCurrentlyAllowed,
- ::Quad
- )
- .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
- when {
- authFlags.isPrimaryAuthRequiredAfterReboot &&
- wasRebootedForMainlineUpdate ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
- authFlags.isPrimaryAuthRequiredAfterReboot ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
- authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
- DeviceEntryRestrictionReason.PolicyLockdown
- authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
- authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
- DeviceEntryRestrictionReason.UnattendedUpdate
- authFlags.isPrimaryAuthRequiredAfterTimeout ->
- DeviceEntryRestrictionReason.SecurityTimeout
- authFlags.isPrimaryAuthRequiredAfterLockout ->
- DeviceEntryRestrictionReason.BouncerLockedOut
- isFingerprintLockedOut ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
- authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
- DeviceEntryRestrictionReason.AdaptiveAuthRequest
- (trustEnabled && !trustManaged) &&
- (authFlags.someAuthRequiredAfterTrustAgentExpired ||
- authFlags.someAuthRequiredAfterUserRequest) ->
- DeviceEntryRestrictionReason.TrustAgentDisabled
- authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
- else -> null
- }
- }
- } else {
- flowOf(null)
- }
- }
-
- /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
- val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
-
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -268,27 +185,6 @@
return repository.isLockscreenEnabled()
}
- fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
- return when (this) {
- DeviceEntryRestrictionReason.UserLockdown -> true
- DeviceEntryRestrictionReason.PolicyLockdown -> true
-
- // Add individual enum value instead of using "else" so new reasons are guaranteed
- // to be added here at compile-time.
- null -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
- DeviceEntryRestrictionReason.BouncerLockedOut -> false
- DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
- DeviceEntryRestrictionReason.TrustAgentDisabled -> false
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
- DeviceEntryRestrictionReason.SecurityTimeout -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
- DeviceEntryRestrictionReason.UnattendedUpdate -> false
- DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
- }
- }
-
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
@@ -296,12 +192,4 @@
* lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
- private val wasRebootedForMainlineUpdate
- get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
-
- companion object {
- @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
- @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5141690..e17e530 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,20 +16,26 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -49,6 +55,8 @@
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
private val deviceUnlockSource =
@@ -69,6 +77,75 @@
.map { DeviceUnlockSource.BouncerInput }
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.isLockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate() ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ biometricSettingsInteractor.authenticationFlags.map { authFlags ->
+ when {
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ else -> null
+ }
+ }
+ }
+ }
+
+ /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+ val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
/**
* Whether the device is unlocked or not, along with the information about the authentication
* method that was used to unlock the device.
@@ -90,13 +167,18 @@
// Device is locked if SIM is locked.
flowOf(DeviceUnlockStatus(false, null))
} else {
- powerInteractor.isAsleep.flatMapLatest { isAsleep ->
- if (isAsleep) {
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ combine(
+ powerInteractor.isAsleep,
+ isInLockdown,
+ ::Pair,
+ )
+ .flatMapLatestConflated { (isAsleep, isInLockdown) ->
+ if (isAsleep || isInLockdown) {
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ }
}
- }
}
}
.stateIn(
@@ -104,4 +186,34 @@
started = SharingStarted.Eagerly,
initialValue = DeviceUnlockStatus(false, null),
)
+
+ private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+ return when (this) {
+ DeviceEntryRestrictionReason.UserLockdown -> true
+ DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+ // Add individual enum value instead of using "else" so new reasons are guaranteed
+ // to be added here at compile-time.
+ null -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+ DeviceEntryRestrictionReason.BouncerLockedOut -> false
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+ DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+ DeviceEntryRestrictionReason.SecurityTimeout -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+ DeviceEntryRestrictionReason.UnattendedUpdate -> false
+ DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+ }
+ }
+
+ private fun wasRebootedForMainlineUpdate(): Boolean {
+ return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+ }
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 0bcfa96..69ddb62 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -44,6 +44,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -137,6 +138,7 @@
override val displayAdditionEvent: Flow<Display?> =
allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map { getDisplay(it.displayId) }
+ // TODO: b/345472038 - Delete after the flag is ramped up.
private val oldEnabledDisplays: Flow<Set<Display>> =
allDisplayEvents
.map { getDisplays() }
@@ -167,6 +169,9 @@
}
.debugLog("enabledDisplayIds")
+ private val defaultDisplay by lazy {
+ getDisplay(Display.DEFAULT_DISPLAY) ?: error("Unable to get default display.")
+ }
/**
* Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
*
@@ -181,11 +186,7 @@
.stateIn(
bgApplicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue =
- setOf(
- getDisplay(Display.DEFAULT_DISPLAY)
- ?: error("Unable to get default display.")
- )
+ initialValue = setOf(defaultDisplay)
)
} else {
oldEnabledDisplays
@@ -344,9 +345,10 @@
.debugLog("pendingDisplay")
override val defaultDisplayOff: Flow<Boolean> =
- displays
- .map { displays -> displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY } }
- .map { it?.state == Display.STATE_OFF }
+ displayChangeEvent
+ .filter { it == Display.DEFAULT_DISPLAY }
+ .map { defaultDisplay.state == Display.STATE_OFF }
+ .distinctUntilChanged()
private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
return if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 8776ec5..17b455d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
+import androidx.annotation.MainThread;
+
/**
* Interface the doze service uses to communicate with the rest of system UI.
*/
@@ -27,6 +29,7 @@
void startDozing();
void pulseWhileDozing(@NonNull PulseCallback callback, int reason);
void stopDozing();
+ @MainThread
void dozeTimeTick();
boolean isPowerSaveActive();
boolean isPulsingBlocked();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 7ae8409..e07b5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,6 +48,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -426,7 +427,11 @@
}
if (!anyListening) {
- mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mSettingsObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ }
} else if (!mSettingRegistered) {
for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
@@ -750,8 +755,13 @@
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
- mSecureSettings.registerContentObserverForUserSync(
- mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 1a06418..9def81a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -27,6 +27,8 @@
import android.text.format.Formatter;
import android.util.Log;
+import androidx.annotation.AnyThread;
+
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
@@ -55,7 +57,7 @@
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
private final DelayableExecutor mBgExecutor;
- private long mLastTimeTickElapsed = 0;
+ private volatile long mLastTimeTickElapsed = 0;
// If time tick is scheduled and there's not a pending runnable to cancel:
private volatile boolean mTimeTickScheduled;
private final Runnable mCancelTimeTickerRunnable = new Runnable() {
@@ -218,10 +220,15 @@
return calendar.getTimeInMillis();
}
+ @AnyThread
private void onTimeTick() {
verifyLastTimeTick();
- mHost.dozeTimeTick();
+ if (dozeuiSchedulingAlarmsBackgroundExecution()) {
+ mHandler.post(mHost::dozeTimeTick);
+ } else {
+ mHost.dozeTimeTick();
+ }
// Keep wakelock until a frame has been pushed.
mHandler.post(mWakeLock.wrap(() -> {}));
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index c6c57479..83fa001 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,6 +45,7 @@
import androidx.lifecycle.ServiceLifecycleDispatcher;
import androidx.lifecycle.ViewModelStore;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.dream.lowlight.dagger.LowLightDreamModule;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -97,7 +98,7 @@
@Nullable
private final ComponentName mHomeControlPanelDreamComponent;
private final UiEventLogger mUiEventLogger;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final String mWindowTitle;
// A reference to the {@link Window} used to hold the dream overlay.
@@ -244,7 +245,7 @@
Context context,
DreamOverlayLifecycleOwner lifecycleOwner,
@Main DelayableExecutor executor,
- WindowManager windowManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
ComplicationComponent.Factory complicationComponentFactory,
com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
dreamComplicationComponentFactory,
@@ -267,7 +268,7 @@
super(executor);
mContext = context;
mExecutor = executor;
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mScrimManager = scrimManager;
mLowLightDreamComponent = lowLightDreamComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index e2bcb6b..53b9261 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -17,8 +17,12 @@
package com.android.systemui.education.dagger
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -26,8 +30,15 @@
@Module
interface ContextualEducationModule {
+ @Binds
+ fun bindContextualEducationRepository(
+ impl: ContextualEducationRepositoryImpl
+ ): ContextualEducationRepository
+
@Qualifier annotation class EduDataStoreScope
+ @Qualifier annotation class EduClock
+
companion object {
@EduDataStoreScope
@Provides
@@ -36,5 +47,11 @@
): CoroutineScope {
return CoroutineScope(bgDispatcher + SupervisorJob())
}
+
+ @EduClock
+ @Provides
+ fun provideEduClock(): Clock {
+ return Clock.systemUTC()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index af35e8c..9f6cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.education.data.model
+import java.time.Instant
+
/**
* Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each
* gesture stores its own model separately.
*/
data class GestureEduModel(
- val signalCount: Int,
- val educationShownCount: Int,
+ val signalCount: Int = 0,
+ val educationShownCount: Int = 0,
+ val lastShortcutTriggeredTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
index c9dd833..248b7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
@@ -17,26 +17,50 @@
package com.android.systemui.education.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Clock
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates the functions of ContextualEducationRepository. */
+interface ContextualEducationRepository {
+ fun setUser(userId: Int)
+
+ fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+ suspend fun incrementSignalCount(gestureType: GestureType)
+
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType)
+}
/**
* Provide methods to read and update on field level and allow setting datastore when user is
* changed
*/
@SysUISingleton
-class ContextualEducationRepository
+class ContextualEducationRepositoryImpl
@Inject
-constructor(private val userEduRepository: UserContextualEducationRepository) {
+constructor(
+ @EduClock private val clock: Clock,
+ private val userEduRepository: UserContextualEducationRepository
+) : ContextualEducationRepository {
/** To change data store when user is changed */
- fun setUser(userId: Int) = userEduRepository.setUser(userId)
+ override fun setUser(userId: Int) = userEduRepository.setUser(userId)
- fun readGestureEduModelFlow(gestureType: GestureType) =
+ override fun readGestureEduModelFlow(gestureType: GestureType) =
userEduRepository.readGestureEduModelFlow(gestureType)
- suspend fun incrementSignalCount(gestureType: GestureType) {
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
userEduRepository.updateGestureEduModel(gestureType) {
it.copy(signalCount = it.signalCount + 1)
}
}
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ userEduRepository.updateGestureEduModel(gestureType) {
+ it.copy(lastShortcutTriggeredTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 229511a..b7fc773 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -18,16 +18,19 @@
import android.content.Context
import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -55,6 +58,7 @@
companion object {
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
+ const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -91,6 +95,10 @@
return GestureEduModel(
signalCount = preferences[getSignalCountKey(gestureType)] ?: 0,
educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
+ lastShortcutTriggeredTime =
+ preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
+ Instant.ofEpochMilli(it)
+ },
)
}
@@ -103,6 +111,11 @@
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
+ updateTimeByInstant(
+ preferences,
+ updatedModel.lastShortcutTriggeredTime,
+ getLastShortcutTriggeredTimeKey(gestureType)
+ )
}
}
@@ -111,4 +124,19 @@
private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX)
+
+ private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
+
+ private fun updateTimeByInstant(
+ preferences: MutablePreferences,
+ instant: Instant?,
+ key: Preferences.Key<Long>
+ ) {
+ if (instant != null) {
+ preferences[key] = instant.toEpochMilli()
+ } else {
+ preferences.remove(key)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 1e4fb4f..493afde 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -64,7 +64,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.ServiceState;
@@ -197,7 +196,6 @@
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
- private final IDreamManager mDreamManager;
private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
private final SelectedUserInteractor mSelectedUserInteractor;
@@ -345,7 +343,6 @@
Context context,
GlobalActionsManager windowManagerFuncs,
AudioManager audioManager,
- IDreamManager iDreamManager,
DevicePolicyManager devicePolicyManager,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -382,7 +379,6 @@
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
- mDreamManager = iDreamManager;
mDevicePolicyManager = devicePolicyManager;
mLockPatternUtils = lockPatternUtils;
mTelephonyListenerManager = telephonyListenerManager;
@@ -510,20 +506,7 @@
mHandler.sendEmptyMessage(MESSAGE_DISMISS);
}
- protected void awakenIfNecessary() {
- if (mDreamManager != null) {
- try {
- if (mDreamManager.isDreaming()) {
- mDreamManager.awaken();
- }
- } catch (RemoteException e) {
- // we tried
- }
- }
- }
-
protected void handleShow(@Nullable Expandable expandable) {
- awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 7b5139a..c44eb47 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,8 +17,10 @@
package com.android.systemui.haptics.qs
import android.os.VibrationEffect
+import android.service.quicksettings.Tile
import androidx.annotation.VisibleForTesting
import com.android.systemui.animation.Expandable
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,6 +42,7 @@
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
+ private val falsingManager: FalsingManager,
) {
var effectDuration = 0
@@ -130,18 +133,18 @@
fun handleAnimationComplete() {
when (state) {
State.RUNNING_FORWARD -> {
- setState(State.IDLE)
vibrate(snapEffect)
if (keyguardStateController.isUnlocked) {
- qsTile?.longClick(expandable)
+ setState(State.LONG_CLICKED)
} else {
callback?.onResetProperties()
- qsTile?.longClick(expandable)
+ setState(State.IDLE)
}
+ qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
- setState(State.IDLE)
callback?.onEffectFinishedReversing()
+ setState(getStateForClick())
qsTile?.click(expandable)
}
State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
@@ -160,14 +163,37 @@
}
fun onTileClick(): Boolean {
- if (state == State.TIMEOUT_WAIT) {
- setState(State.IDLE)
- qsTile?.let {
- it.click(expandable)
- return true
- }
+ val isStateClickable = state == State.TIMEOUT_WAIT || state == State.IDLE
+
+ // Ignore View-generated clicks on invalid states or if the bouncer is showing
+ if (keyguardStateController.isPrimaryBouncerShowing || !isStateClickable) return false
+
+ setState(getStateForClick())
+ qsTile?.click(expandable)
+ return true
+ }
+
+ /**
+ * Get the appropriate state for a click action.
+ *
+ * In some occasions, the click action will not result in a subsequent action that resets the
+ * state upon completion (e.g., a launch transition animation). In these cases, the state needs
+ * to be reset before the click is dispatched.
+ */
+ @VisibleForTesting
+ fun getStateForClick(): State {
+ val isTileUnavailable = qsTile?.state?.state == Tile.STATE_UNAVAILABLE
+ val isFalseTapWhileLocked =
+ !keyguardStateController.isUnlocked &&
+ falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ val handlesLongClick = qsTile?.state?.handlesLongClick == true
+ return if (isTileUnavailable || isFalseTapWhileLocked || !handlesLongClick) {
+ // The click event will not perform an action that resets the state. Therefore, this is
+ // the last opportunity to reset the state back to IDLE.
+ State.IDLE
+ } else {
+ State.CLICKED
}
- return false
}
/**
@@ -194,6 +220,8 @@
return true
}
+ fun resetState() = setState(State.IDLE)
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
@@ -202,6 +230,8 @@
RUNNING_BACKWARDS_FROM_UP,
/* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
RUNNING_BACKWARDS_FROM_CANCEL,
+ CLICKED, /* The effect has ended with a click */
+ LONG_CLICKED, /* The effect has ended with a long-click */
}
/** Callbacks to notify view and animator actions */
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
index 1f0aef8..906f600 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -21,11 +21,13 @@
import com.android.systemui.Flags.keyboardShortcutHelperRewrite
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -55,6 +57,10 @@
fun multitaskingShortcutsSource(impl: MultitaskingShortcutsSource): KeyboardShortcutGroupsSource
@Binds
+ @CurrentAppShortcuts
+ fun currentAppShortcutsSource(impl: CurrentAppShortcutsSource): KeyboardShortcutGroupsSource
+
+ @Binds
@InputShortcuts
fun inputShortcutsSources(impl: InputShortcutsSource): KeyboardShortcutGroupsSource
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 7b0c25e..49817b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -20,6 +20,7 @@
import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.util.Log
+import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
@@ -28,16 +29,18 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.APP_CATEGORIES
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
@@ -45,7 +48,11 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -53,11 +60,13 @@
@Inject
constructor(
private val context: Context,
+ @Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
@MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
@AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
@InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
+ @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
private val inputManager: InputManager,
stateRepository: ShortcutHelperStateRepository
) {
@@ -71,61 +80,82 @@
}
}
- val systemShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- SYSTEM,
- systemShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
+ val categories: Flow<List<ShortcutCategory>> =
+ activeInputDevice
+ .map {
+ if (it == null) {
+ return@map emptyList()
+ }
+ return@map listOfNotNull(
+ fetchSystemShortcuts(it),
+ fetchMultiTaskingShortcuts(it),
+ fetchAppCategoriesShortcuts(it),
+ fetchImeShortcuts(it),
+ fetchCurrentAppShortcuts(it),
)
- } else {
- null
}
- }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ initialValue = emptyList(),
+ )
- val multitaskingShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- MULTI_TASKING,
- multitaskingShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
- )
- } else {
- null
- }
- }
+ private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ System,
+ systemShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
- val appCategoriesShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- APP_CATEGORIES,
- appCategoriesShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
- )
- } else {
- null
- }
- }
+ private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ MultiTasking,
+ multitaskingShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
- val imeShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- IME,
- inputShortcutsSource.shortcutGroups(it.id),
- keepIcons = false,
- )
- } else {
- null
- }
+ private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ AppCategories,
+ appCategoriesShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
+
+ private suspend fun fetchImeShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ InputMethodEditor,
+ inputShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = false,
+ )
+
+ private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? {
+ val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id)
+ val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
+ return if (categoryType == null) {
+ null
+ } else {
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ categoryType,
+ shortcutGroups,
+ keepIcons = false
+ )
}
+ }
+
+ private fun getCurrentAppShortcutCategoryType(
+ shortcutGroups: List<KeyboardShortcutGroup>
+ ): ShortcutCategoryType? {
+ return if (shortcutGroups.isEmpty()) {
+ null
+ } else {
+ CurrentApp(packageName = shortcutGroups[0].packageName.toString())
+ }
+ }
private fun toShortcutCategory(
keyCharacterMap: KeyCharacterMap,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index 0aced8c..cbe6fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -222,7 +222,7 @@
{ context ->
context.getString(R.string.keyboard_key_forward_del)
},
- KEYCODE_ESCAPE to { "Esc" },
+ KEYCODE_ESCAPE to { context -> context.getString(R.string.keyboard_key_esc) },
KEYCODE_SYSRQ to { "SysRq" },
KEYCODE_BREAK to { "Break" },
KEYCODE_SCROLL_LOCK to { "Scroll Lock" },
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt
new file mode 100644
index 0000000..7e6ed19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSource.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.source
+
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager
+import android.view.WindowManager.KeyboardShortcutsReceiver
+import javax.inject.Inject
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+class CurrentAppShortcutsSource @Inject constructor(private val windowManager: WindowManager) :
+ KeyboardShortcutGroupsSource {
+ override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
+ suspendCancellableCoroutine { continuation ->
+ val shortcutsReceiver = KeyboardShortcutsReceiver {
+ continuation.resumeWith(Result.success(it ?: emptyList()))
+ }
+ windowManager.requestAppKeyboardShortcuts(shortcutsReceiver, deviceId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
index aba4415..1b20986 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
@@ -59,7 +59,7 @@
private suspend fun getImeShortcutGroup(deviceId: Int): List<KeyboardShortcutGroup> =
suspendCancellableCoroutine { continuation ->
val shortcutsReceiver = KeyboardShortcutsReceiver {
- continuation.resumeWith(Result.success(it))
+ continuation.resumeWith(Result.success(it ?: emptyList()))
}
windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index d41d21a..6f19561 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -23,7 +23,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@@ -33,13 +33,8 @@
) {
val shortcutCategories: Flow<List<ShortcutCategory>> =
- combine(
- categoriesRepository.systemShortcutsCategory,
- categoriesRepository.multitaskingShortcutsCategory,
- categoriesRepository.imeShortcutsCategory,
- categoriesRepository.appCategoriesShortcutsCategory,
- ) { shortcutCategories ->
- shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
+ categoriesRepository.categories.map { categories ->
+ categories.map { category -> groupSubCategoriesInCategory(category) }
}
private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
similarity index 79%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
index c92234c..51631b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CurrentAppShortcuts.kt
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.keyboard.shortcut.qualifiers
import javax.inject.Qualifier
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Qualifier annotation class CurrentAppShortcuts
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 63e167a..4eabefc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -16,11 +16,16 @@
package com.android.systemui.keyboard.shortcut.shared.model
-enum class ShortcutCategoryType {
- SYSTEM,
- MULTI_TASKING,
- IME,
- APP_CATEGORIES,
+sealed interface ShortcutCategoryType {
+ data object System : ShortcutCategoryType
+
+ data object MultiTasking : ShortcutCategoryType
+
+ data object InputMethodEditor : ShortcutCategoryType
+
+ data object AppCategories : ShortcutCategoryType
+
+ data class CurrentApp(val packageName: String) : ShortcutCategoryType
}
data class ShortcutCategory(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 3b037bc..b482862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -16,7 +16,10 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import android.content.Context
+import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Icon
+import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
@@ -55,6 +58,7 @@
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationDrawerItemColors
import androidx.compose.material3.NavigationDrawerItemDefaults
@@ -76,7 +80,6 @@
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
@@ -99,8 +102,10 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.CentralSurfaces
@Composable
fun ShortcutHelper(
@@ -115,6 +120,7 @@
ShortcutHelperSinglePane(
modifier,
shortcutsUiState.shortcutCategories,
+ shortcutsUiState.defaultSelectedCategory,
onKeyboardSettingsClicked
)
} else {
@@ -141,6 +147,7 @@
private fun ShortcutHelperSinglePane(
modifier: Modifier = Modifier,
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
onKeyboardSettingsClicked: () -> Unit,
) {
Column(
@@ -154,7 +161,7 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar()
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(categories)
+ CategoriesPanelSinglePane(categories, defaultSelectedCategory)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onClick = onKeyboardSettingsClicked)
}
@@ -163,8 +170,10 @@
@Composable
private fun CategoriesPanelSinglePane(
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
) {
- var expandedCategory by remember { mutableStateOf<ShortcutCategory?>(null) }
+ val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
+ var expandedCategory by remember { mutableStateOf(selectedCategory) }
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
categories.fastForEachIndexed { index, category ->
val isExpanded = expandedCategory == category
@@ -210,9 +219,9 @@
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
) {
- Icon(category.icon, contentDescription = null)
+ ShortcutCategoryIcon(category.icon)
Spacer(modifier = Modifier.width(16.dp))
- Text(stringResource(category.labelResId))
+ Text(category.label(LocalContext.current))
Spacer(modifier = Modifier.weight(1f))
RotatingExpandCollapseIcon(isExpanded)
}
@@ -221,23 +230,67 @@
}
}
-private val ShortcutCategory.icon: ImageVector
+private val ShortcutCategory.icon: IconSource
+ @Composable
get() =
when (type) {
- ShortcutCategoryType.SYSTEM -> Icons.Default.Tv
- ShortcutCategoryType.MULTI_TASKING -> Icons.Default.VerticalSplit
- ShortcutCategoryType.IME -> Icons.Default.Keyboard
- ShortcutCategoryType.APP_CATEGORIES -> Icons.Default.Apps
+ ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
+ ShortcutCategoryType.MultiTasking ->
+ IconSource(imageVector = Icons.Default.VerticalSplit)
+ ShortcutCategoryType.InputMethodEditor ->
+ IconSource(imageVector = Icons.Default.Keyboard)
+ ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
+ is ShortcutCategoryType.CurrentApp -> {
+ val context = LocalContext.current
+ val iconDrawable = context.packageManager.getApplicationIcon(type.packageName)
+ IconSource(painter = rememberDrawablePainter(drawable = iconDrawable))
+ }
}
-private val ShortcutCategory.labelResId: Int
- get() =
- when (type) {
- ShortcutCategoryType.SYSTEM -> R.string.shortcut_helper_category_system
- ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_multitasking
- ShortcutCategoryType.IME -> R.string.shortcut_helper_category_input
- ShortcutCategoryType.APP_CATEGORIES -> R.string.shortcut_helper_category_app_shortcuts
- }
+@Composable
+fun ShortcutCategoryIcon(
+ source: IconSource,
+ contentDescription: String? = null,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current
+) {
+ if (source.imageVector != null) {
+ Icon(source.imageVector, contentDescription, modifier, tint)
+ } else if (source.painter != null) {
+ Image(source.painter, contentDescription, modifier)
+ }
+}
+
+private fun ShortcutCategory.label(context: Context): String =
+ when (type) {
+ ShortcutCategoryType.System -> context.getString(R.string.shortcut_helper_category_system)
+ ShortcutCategoryType.MultiTasking ->
+ context.getString(R.string.shortcut_helper_category_multitasking)
+ ShortcutCategoryType.InputMethodEditor ->
+ context.getString(R.string.shortcut_helper_category_input)
+ ShortcutCategoryType.AppCategories ->
+ context.getString(R.string.shortcut_helper_category_app_shortcuts)
+ is ShortcutCategoryType.CurrentApp -> getApplicationLabelForCurrentApp(type, context)
+ }
+
+private fun getApplicationLabelForCurrentApp(
+ type: ShortcutCategoryType.CurrentApp,
+ context: Context
+): String {
+ val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
+ return try {
+ val currentAppInfo =
+ packageManagerForUser.getApplicationInfoAsUser(
+ type.packageName,
+ /* flags = */ 0,
+ context.userId
+ )
+ packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
+ } catch (e: NameNotFoundException) {
+ Log.wtf(ShortcutHelper.TAG, "Couldn't find app info by package name ${type.packageName}")
+ context.getString(R.string.shortcut_helper_category_current_app_shortcuts)
+ }
+}
@Composable
private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
@@ -525,8 +578,8 @@
Column {
categories.fastForEach {
CategoryItemTwoPane(
- label = stringResource(it.labelResId),
- icon = it.icon,
+ label = it.label(LocalContext.current),
+ iconSource = it.icon,
selected = selectedCategory == it.type,
onClick = { onCategoryClicked(it) }
)
@@ -537,7 +590,7 @@
@Composable
private fun CategoryItemTwoPane(
label: String,
- icon: ImageVector,
+ iconSource: IconSource,
selected: Boolean,
onClick: () -> Unit,
colors: NavigationDrawerItemColors =
@@ -551,9 +604,9 @@
color = colors.containerColor(selected).value,
) {
Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
- Icon(
+ ShortcutCategoryIcon(
modifier = Modifier.size(24.dp),
- imageVector = icon,
+ source = iconSource,
contentDescription = null,
tint = colors.iconColor(selected).value
)
@@ -649,4 +702,6 @@
object Dimensions {
val SinglePaneCategoryCornerRadius = 28.dp
}
+
+ internal const val TAG = "ShortcutHelperUI"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
index c92234c..7fc0103 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/IconSource.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.keyboard.shortcut.ui.model
-import javax.inject.Qualifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+data class IconSource(val imageVector: ImageVector? = null, val painter: Painter? = null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index e602cad..25574ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -40,8 +42,8 @@
) {
val shouldShow =
- stateInteractor.state
- .map { it is ShortcutHelperState.Active }
+ categoriesInteractor.shortcutCategories
+ .map { it.isNotEmpty() }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
@@ -53,7 +55,7 @@
} else {
ShortcutsUiState.Active(
shortcutCategories = it,
- defaultSelectedCategory = it.first().type,
+ defaultSelectedCategory = getDefaultSelectedCategory(it),
)
}
}
@@ -63,6 +65,13 @@
initialValue = ShortcutsUiState.Inactive
)
+ private fun getDefaultSelectedCategory(
+ categories: List<ShortcutCategory>
+ ): ShortcutCategoryType {
+ val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ return currentAppShortcuts?.type ?: categories.first().type
+ }
+
fun onViewClosed() {
stateInteractor.onViewClosed()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 33f9209..80cf4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -21,6 +21,7 @@
import android.view.LayoutInflater
import android.view.View
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintSet
@@ -29,9 +30,9 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.transitions
import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
@@ -115,7 +116,6 @@
private var rootViewHandle: DisposableHandle? = null
private var indicationAreaHandle: DisposableHandle? = null
- private val sceneKey = SceneKey("root-view-scene-key")
var keyguardStatusViewController: KeyguardStatusViewController? = null
get() {
@@ -233,12 +233,10 @@
setContent {
// STL is used solely to provide a SceneScope to enable us to invoke SceneScope
// composables.
- SceneTransitionLayout(
- currentScene = sceneKey,
- onChangeScene = {},
- transitions = transitions {},
- ) {
- scene(sceneKey) {
+ val currentScene = remember { SceneKey("root-view-scene-key") }
+ val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+ SceneTransitionLayout(state) {
+ scene(currentScene) {
with(
LockscreenContent(
viewModel = viewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe81b20c..e46a7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -78,7 +78,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -151,6 +150,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
@@ -360,6 +360,7 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
+ private final ProcessWrapper mProcessWrapper;
private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
@@ -1459,10 +1460,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -1487,9 +1490,9 @@
mSecureSettings = secureSettings;
mSystemSettings = systemSettings;
mSystemClock = systemClock;
+ mProcessWrapper = processWrapper;
mSystemPropertiesHelper = systemPropertiesHelper;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mStatusBarService = statusBarService;
mKeyguardDisplayManager = keyguardDisplayManager;
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
@@ -2834,6 +2837,14 @@
*/
private void handleShow(Bundle options) {
Trace.beginSection("KeyguardViewMediator#handleShow");
+ try {
+ handleShowInner(options);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleShowInner(Bundle options) {
final boolean showUnlocked = options != null
&& options.getBoolean(OPTION_SHOW_DISMISSIBLE, false);
final int currentUser = mSelectedUserInteractor.getSelectedUserId();
@@ -2885,8 +2896,6 @@
mKeyguardDisplayManager.show();
scheduleNonStrongBiometricIdleTimeout();
-
- Trace.endSection();
}
/**
@@ -3065,6 +3074,17 @@
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
+ try {
+ handleStartKeyguardExitAnimationInner(startTime, fadeoutDuration, apps, wallpapers,
+ nonApps, finishedCallback);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleStartKeyguardExitAnimationInner(long startTime, long fadeoutDuration,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
synchronized (KeyguardViewMediator.this) {
@@ -3253,8 +3273,6 @@
onKeyguardExitFinished();
}
}
-
- Trace.endSection();
}
private void onKeyguardExitFinished() {
@@ -3496,12 +3514,20 @@
// TODO (b/155663717) After restart, status bar will not properly hide home button
// unless disable is called to show un-hide it once first
if (forceClearFlags) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ } else {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to force clear flags", e);
+ }
}
}
@@ -3525,6 +3551,14 @@
}
if (!SceneContainerFlag.isEnabled()) {
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
mContext.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 97ea16d..b9e5135 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -24,11 +24,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.utils.GlobalWindowManager
import javax.inject.Inject
@@ -52,24 +48,15 @@
private val globalWindowManager: GlobalWindowManager,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val sceneInteractor: SceneInteractor,
) : CoreStartable {
override fun start() {
Log.d(LOG_TAG, "Resource trimmer registered.")
applicationScope.launch(bgDispatcher) {
- // We drop 1 to avoid triggering on initial collect().
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.transitionState
- .filter { it.isIdle(Scenes.Gone) }
- .collect { onKeyguardGone() }
- } else {
- keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect {
- if (it.transitionState == TransitionState.FINISHED) {
- onKeyguardGone()
- }
- }
- }
+ keyguardTransitionInteractor
+ .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .filter { isOnGone -> isOnGone }
+ .collect { onKeyguardGone() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 15dac09..a43bfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,6 +65,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -158,10 +160,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -206,10 +210,12 @@
activityTransitionAnimator,
scrimControllerLazy,
activityTaskManagerService,
+ statusBarService,
featureFlags,
secureSettings,
systemSettings,
systemClock,
+ processWrapper,
mainDispatcher,
dreamViewModel,
communalTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 1167cc4..893835a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -137,6 +137,8 @@
(KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
if (shouldTransitionToGone) {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ if (SceneContainerFlag.isEnabled) return@collect
startTransitionTo(
toState = KeyguardState.GONE,
)
@@ -197,6 +199,7 @@
* PRIMARY_BOUNCER.
*/
private fun listenForAodToPrimaryBouncer() {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch("$TAG#listenForAodToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
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 aee65a8..cd28bec 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
@@ -17,8 +17,11 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -28,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
@@ -53,9 +57,11 @@
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
+ private val dreamManager: DreamManager,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -115,6 +121,7 @@
}
}
+ @SuppressLint("MissingPermission")
private fun listenForDozingToAny() {
if (KeyguardWmStateRefactor.isEnabled) {
return
@@ -126,7 +133,8 @@
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
keyguardInteractor.isKeyguardOccluded,
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
@@ -134,6 +142,7 @@
(
_,
occluded,
+ isCommunalAvailable,
isIdleOnCommunal,
canTransitionToGoneOnWake,
primaryBouncerShowing) ->
@@ -163,6 +172,19 @@
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
+ } else if (
+ powerInteractor.detailedWakefulness.value.lastWakeReason ==
+ WakeSleepReason.POWER_BUTTON &&
+ isCommunalAvailable &&
+ dreamManager.canStartDreaming(true)
+ ) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ if (SceneContainerFlag.isEnabled) {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ } else {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
@@ -171,6 +193,7 @@
}
/** Figure out what state to transition to when we awake from DOZING. */
+ @SuppressLint("MissingPermission")
private fun listenForWakeFromDozing() {
if (!KeyguardWmStateRefactor.isEnabled) {
return
@@ -180,7 +203,8 @@
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
wakeToGoneInteractor.canWakeDirectlyToGone,
keyguardInteractor.primaryBouncerShowing,
@@ -188,6 +212,7 @@
.collect {
(
_,
+ isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
canWakeDirectlyToGone,
@@ -227,6 +252,23 @@
ownerReason = "waking from dozing"
)
}
+ } else if (
+ powerInteractor.detailedWakefulness.value.lastWakeReason ==
+ WakeSleepReason.POWER_BUTTON &&
+ isCommunalAvailable &&
+ dreamManager.canStartDreaming(true)
+ ) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ if (SceneContainerFlag.isEnabled) {
+ // TODO(b/336576536): Check if adaptation for scene framework is
+ // needed
+ } else {
+ startTransitionTo(
+ KeyguardState.GLANCEABLE_HUB,
+ ownerReason = "waking from dozing"
+ )
+ }
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
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 8041dd9..2eca9e1 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
@@ -167,6 +167,8 @@
}
private fun listenForHubToGone() {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
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 ff15a1b..343a135 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
@@ -272,10 +272,9 @@
}
private fun listenForLockscreenToGone() {
- if (KeyguardWmStateRefactor.isEnabled) {
- return
- }
-
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ if (SceneContainerFlag.isEnabled) return
+ if (KeyguardWmStateRefactor.isEnabled) return
scope.launch("$TAG#listenForLockscreenToGone") {
keyguardInteractor.isKeyguardGoingAway
.filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
@@ -289,6 +288,7 @@
}
private fun listenForLockscreenToGoneDragging() {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (KeyguardWmStateRefactor.isEnabled) {
// When the refactor is enabled, we no longer use isKeyguardGoingAway.
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 84ca667..f3ca9df 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
@@ -71,6 +71,8 @@
}
private fun listenForOccludedToPrimaryBouncer() {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
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 f98ed15..2429088 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
@@ -40,7 +40,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -81,25 +80,18 @@
}
val surfaceBehindVisibility: Flow<Boolean?> =
- if (SceneContainerFlag.isEnabled) {
- // The edge Scenes.Bouncer <-> Scenes.Gone is handled by STL
- flowOf(null)
- } else {
- transitionInteractor
- .transition(
- edge = Edge.INVALID,
- edgeWithoutSceneContainer =
- Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
- )
- .map<TransitionStep, Boolean?> {
- it.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
- }
- .onStart {
- // Default to null ("don't care, use a reasonable default").
- emit(null)
- }
- .distinctUntilChanged()
- }
+ transitionInteractor
+ .transition(
+ edge = Edge.INVALID,
+ edgeWithoutSceneContainer =
+ Edge.create(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
+ )
+ .map<TransitionStep, Boolean?> { it.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
fun dismissPrimaryBouncer() {
scope.launch { startTransitionTo(KeyguardState.GONE) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 41c3959..e2bb540 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.domain.interactor
-import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -32,15 +31,12 @@
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -51,13 +47,10 @@
constructor(
private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
@Application private val applicationScope: CoroutineScope,
- private val context: Context,
- private val shadeInteractor: ShadeInteractor,
- private val clockInteractor: KeyguardClockInteractor,
+ shadeInteractor: ShadeInteractor,
private val configurationInteractor: ConfigurationInteractor,
private val fingerprintPropertyInteractor: FingerprintPropertyInteractor,
private val smartspaceSection: SmartspaceSection,
- private val clockSection: ClockSection,
) : CoreStartable {
/** The current blueprint for the lockscreen. */
val blueprint: StateFlow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
@@ -70,8 +63,8 @@
/** Current BlueprintId */
val blueprintId =
- shadeInteractor.shadeMode.map { shadeMode ->
- val useSplitShade = shadeMode == ShadeMode.Split && !ComposeLockscreen.isEnabled
+ shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
+ val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled
when {
useSplitShade -> SplitShadeKeyguardBlueprint.ID
else -> DefaultKeyguardBlueprint.DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 142b1a0..c0049d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.util.kotlin.combine
@@ -45,6 +44,7 @@
import kotlinx.coroutines.flow.stateIn
private val TAG = KeyguardClockInteractor::class.simpleName
+
/** Manages and encapsulates the clock components of the lockscreen root view. */
@SysUISingleton
class KeyguardClockInteractor
@@ -77,16 +77,16 @@
val clockSize: StateFlow<ClockSize> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
mediaCarouselInteractor.hasActiveMediaOrRecommendation,
keyguardInteractor.isDozing,
isOnAod,
- ) { shadeMode, hasNotifs, hasMedia, isDozing, isOnAod ->
+ ) { isShadeLayoutWide, hasNotifs, hasMedia, isDozing, isOnAod ->
return@combine when {
keyguardClockRepository.shouldForceSmallClock && !isOnAod -> ClockSize.SMALL
- shadeMode == ShadeMode.Single && (hasNotifs || hasMedia) -> ClockSize.SMALL
- shadeMode == ShadeMode.Single -> ClockSize.LARGE
+ !isShadeLayoutWide && (hasNotifs || hasMedia) -> ClockSize.SMALL
+ !isShadeLayoutWide -> ClockSize.LARGE
hasMedia && !isDozing -> ClockSize.SMALL
else -> ClockSize.LARGE
}
@@ -103,21 +103,21 @@
val clockShouldBeCentered: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
keyguardInteractor.isActiveDreamLockscreenHosted,
isOnAod,
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
keyguardInteractor.isDozing,
) {
- shadeMode,
+ isShadeLayoutWide,
areAnyNotificationsPresent,
isActiveDreamLockscreenHosted,
isOnAod,
isHeadsUp,
isDozing ->
when {
- shadeMode != ShadeMode.Split -> true
+ !isShadeLayoutWide -> true
!areAnyNotificationsPresent -> true
isActiveDreamLockscreenHosted -> true
// Pulsing notification appears on the right. Move clock left to avoid overlap.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 1aac1c5..f4d8265 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -25,9 +25,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -35,6 +34,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.map
@@ -51,7 +51,6 @@
transitionInteractor: KeyguardTransitionInteractor,
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
- sceneInteractor: SceneInteractor,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -76,11 +75,10 @@
)
private val finishedTransitionToGone: Flow<Unit> =
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {}
- } else {
- transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {}
- }
+ transitionInteractor
+ .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .filter { it }
+ .map {}
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
@@ -90,12 +88,24 @@
.sample(dismissAction)
.filterNot { it is DismissAction.None }
.map { it.onDismissAction }
+
val resetDismissAction: Flow<Unit> =
- transitionInteractor.finishedKeyguardTransitionStep
- .filter { it.to != ALTERNATE_BOUNCER && it.to != PRIMARY_BOUNCER && it.to != GONE }
- .sample(dismissAction)
- .filterNot { it is DismissAction.None }
- .map {} // map to Unit
+ combine(
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = GONE
+ ),
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Bouncer,
+ stateWithoutSceneContainer = PRIMARY_BOUNCER
+ ),
+ transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER)
+ ) { isOnGone, isOnBouncer, isOnAltBouncer ->
+ !isOnGone && !isOnBouncer && !isOnAltBouncer
+ }
+ .filter { it }
+ .sampleFilter(dismissAction) { it !is DismissAction.None }
+ .map {}
fun runDismissAnimationOnKeyguard(): Boolean {
return willAnimateDismissActionOnLockscreen.value
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 66efde1..4aef808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -90,17 +90,13 @@
* GONE.
*/
scope.launch {
- repository.isKeyguardEnabled
- .filter { enabled -> !enabled }
- .sampleCombine(
- biometricSettingsRepository.isCurrentUserInLockdown,
- internalTransitionInteractor.currentTransitionInfoInternal,
- )
- .collect { (_, inLockdown, currentTransitionInfo) ->
- if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
+ if (!SceneContainerFlag.isEnabled) {
+ showKeyguardWhenReenabled
+ .filter { shouldDismiss -> shouldDismiss }
+ .collect {
transitionInteractor.startDismissKeyguardTransition("keyguard disabled")
}
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 5af38ba..7f1e881 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -97,8 +97,8 @@
powerInteractor.detailedWakefulness
.sample(
transitionInteractor.isFinishedIn(
- Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
::Pair
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index bcbdc9c..377a03e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -58,12 +58,12 @@
val viewParams: Flow<KeyguardSurfaceBehindModel> =
combine(
transitionInteractor.isInTransition(
- Edge.create(to = Scenes.Gone),
- edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
+ edge = Edge.create(to = Scenes.Gone),
+ edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
),
transitionInteractor.isFinishedIn(
- Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
notificationLaunchInteractor.isLaunchAnimationRunning,
) { transitioningToGone, isOnGone, notifAnimationRunning ->
@@ -112,8 +112,8 @@
notificationLaunchInteractor.isLaunchAnimationRunning
.sample(
transitionInteractor.isFinishedIn(
- Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
::Pair
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index 6a1b7cf..f0bf402 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -257,6 +258,10 @@
/** Set an alarm for */
private fun setResetCanIgnoreAuthAlarm() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
val intent =
Intent(DELAYED_KEYGUARD_ACTION).apply {
setPackage(context.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 0985e69..e1b333d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -82,6 +82,7 @@
private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
transitionInteractor.startedKeyguardTransitionStep
.flatMapLatest { startedStep ->
+ SceneContainerFlag.assertInLegacyMode()
when (startedStep.from) {
KeyguardState.LOCKSCREEN -> {
fromLockscreenInteractor.surfaceBehindVisibility
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 8db6a43..24db3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -61,6 +61,11 @@
* The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their
* credentials.
*/
+ @Deprecated(
+ "This state won't exist anymore when scene container gets enabled. If you are " +
+ "writing prod code today, make sure to either use flag aware APIs in " +
+ "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+ )
PRIMARY_BOUNCER,
/**
* Device is actively displaying keyguard UI and is not in low-power mode. Device may be
@@ -72,12 +77,22 @@
* hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
* or dream, as well as swipe down for the notifications and up for the bouncer.
*/
+ @Deprecated(
+ "This state won't exist anymore when scene container gets enabled. If you are " +
+ "writing prod code today, make sure to either use flag aware APIs in " +
+ "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+ )
GLANCEABLE_HUB,
/**
* Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is
* being removed, but there are other cases where the user is swiping away keyguard, such as
* with SWIPE security method or face unlock without bypass.
*/
+ @Deprecated(
+ "This state won't exist anymore when scene container gets enabled. If you are " +
+ "writing prod code today, make sure to either use flag aware APIs in " +
+ "[KeyguardTransitionInteractor] or flag appropriately with [SceneContainerFlag]."
+ )
GONE,
/**
* Only used in scene framework. This means we are currently on any scene framework scene that
@@ -143,6 +158,7 @@
/** Whether the lockscreen is visible when we're FINISHED in the given state. */
fun lockscreenVisibleInState(state: KeyguardState): Boolean {
+ // TODO(b/349784682): Transform deprecated states for Flexiglass
return state != GONE
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index 853f176..1fd609d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -26,6 +26,7 @@
import android.view.WindowManager
import android.widget.ProgressBar
import androidx.core.view.isGone
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import javax.inject.Inject
@@ -37,7 +38,7 @@
@Inject
constructor(
private val layoutInflater: LayoutInflater,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
) {
private var overlayView: View? = null
@@ -90,7 +91,7 @@
) {
if (overlayView == null) {
overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
- windowManager.addView(overlayView, overlayViewParams)
+ windowManager.addView(requireNotNull(overlayView), overlayViewParams)
progressBar?.pivotX = 0.0f
progressBar?.pivotY = 0.0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index caa0436..8485a26 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -75,9 +76,12 @@
return animationFlow
.setup(
duration = duration,
- // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> scene
- // transition
- edge = Edge.create(from = from, to = Scenes.Gone)
+ edge =
+ if (from == PRIMARY_BOUNCER) {
+ Edge.INVALID
+ } else {
+ Edge.create(from = from, to = Scenes.Gone)
+ }
)
.setupWithoutSceneContainer(
edge = Edge.create(from = from, to = GONE),
@@ -105,9 +109,12 @@
animationFlow
.setup(
duration = duration,
- // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene ->
- // scene transition
- edge = Edge.create(from = fromState, to = Scenes.Gone),
+ edge =
+ if (fromState == PRIMARY_BOUNCER) {
+ Edge.INVALID
+ } else {
+ Edge.create(from = fromState, to = Scenes.Gone)
+ }
)
.setupWithoutSceneContainer(
edge = Edge.create(from = fromState, to = GONE),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 754ed6c..1ee0368 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -45,6 +46,13 @@
edge = Edge.create(from = DREAMING, to = AOD),
)
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ )
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
index 8eab406..6a3573a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToGlanceableHubTransitionViewModel.kt
@@ -23,7 +23,6 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -37,7 +36,7 @@
private val transitionAnimation =
animationFlow
- .setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.create(GONE, Scenes.Communal))
+ .setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.INVALID)
.setupWithoutSceneContainer(edge = Edge.create(GONE, GLANCEABLE_HUB))
override val deviceEntryParentViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 573b75e..73028c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
import javax.inject.Inject
@@ -119,26 +118,25 @@
combine(
isLargeClockVisible,
clockShouldBeCentered,
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
currentClock,
- ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
- val shouldUseSplitShade = shadeMode == ShadeMode.Split
+ ) { isLargeClockVisible, clockShouldBeCentered, isShadeLayoutWide, currentClock ->
if (currentClock?.config?.useCustomClockScene == true) {
when {
- shouldUseSplitShade && clockShouldBeCentered ->
+ isShadeLayoutWide && clockShouldBeCentered ->
ClockLayout.WEATHER_LARGE_CLOCK
- shouldUseSplitShade && isLargeClockVisible ->
+ isShadeLayoutWide && isLargeClockVisible ->
ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK
- shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+ isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
isLargeClockVisible -> ClockLayout.WEATHER_LARGE_CLOCK
else -> ClockLayout.SMALL_CLOCK
}
} else {
when {
- shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
- shouldUseSplitShade && isLargeClockVisible ->
+ isShadeLayoutWide && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK
+ isShadeLayoutWide && isLargeClockVisible ->
ClockLayout.SPLIT_SHADE_LARGE_CLOCK
- shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
+ isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK
isLargeClockVisible -> ClockLayout.LARGE_CLOCK
else -> ClockLayout.SMALL_CLOCK
}
@@ -164,7 +162,7 @@
/** Calculates the top margin for the small clock. */
fun getSmallClockTopMargin(): Int {
val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard()
- return if (shadeInteractor.shadeMode.value == ShadeMode.Split) {
+ return if (shadeInteractor.isShadeLayoutWide.value) {
resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) -
if (ComposeLockscreen.isEnabled) statusBarHeight else 0
} else {
@@ -176,7 +174,7 @@
val smallClockTopMargin =
combine(
configurationInteractor.onAnyConfigurationChange,
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
) { _, _ ->
getSmallClockTopMargin()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 0fb29c2..350ceb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -91,8 +91,9 @@
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
- private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -160,7 +161,7 @@
edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
),
keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Lockscreen),
+ edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
edgeWithoutSceneContainer =
Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
),
@@ -243,6 +244,7 @@
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 8a29f96..4bfefda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.compose.animation.scene.SceneKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
@@ -26,15 +27,17 @@
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -56,27 +59,7 @@
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
- val shouldUseSplitNotificationShade: StateFlow<Boolean> =
- shadeInteractor.shadeMode
- .map { it == ShadeMode.Split }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
- val areNotificationsVisible: StateFlow<Boolean> =
- combine(
- clockSize,
- shouldUseSplitNotificationShade,
- ) { clockSize, shouldUseSplitNotificationShade ->
- clockSize == ClockSize.SMALL || shouldUseSplitNotificationShade
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
+ val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
/** Amount of horizontal translation that should be applied to elements in the scene. */
val unfoldTranslations: StateFlow<UnfoldTranslations> =
@@ -105,6 +88,25 @@
initialValue = true,
)
+ /**
+ * Returns a flow that indicates whether lockscreen notifications should be rendered in the
+ * given [sceneKey].
+ */
+ fun areNotificationsVisible(sceneKey: SceneKey): Flow<Boolean> {
+ // `Scenes.NotificationsShade` renders its own separate notifications stack, so when it's
+ // open we avoid rendering the lockscreen notifications stack.
+ if (sceneKey == Scenes.NotificationsShade) {
+ return flowOf(false)
+ }
+
+ return combine(
+ clockSize,
+ shadeInteractor.isShadeLayoutWide,
+ ) { clockSize, isShadeLayoutWide ->
+ clockSize == ClockSize.SMALL || isShadeLayoutWide
+ }
+ }
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
index 754fb94..9ec15dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -23,7 +23,6 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -33,10 +32,7 @@
constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow
- .setup(
- duration = TO_GLANCEABLE_HUB_DURATION,
- edge = Edge.create(PRIMARY_BOUNCER, Scenes.Communal)
- )
+ .setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.INVALID)
.setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, GLANCEABLE_HUB))
override val deviceEntryParentViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 7ae4558..46ba5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -26,7 +26,6 @@
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
import javax.inject.Inject
@@ -54,7 +53,7 @@
animationFlow
.setup(
duration = TO_GONE_DURATION,
- edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Gone),
+ edge = Edge.INVALID,
)
.setupWithoutSceneContainer(
edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE),
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index d848b43..e8ded03 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,6 +37,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
@@ -63,6 +65,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
+ private final ProcessWrapper mProcessWrapper;
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
private boolean mKeyguardSessionStarted;
@@ -73,13 +76,15 @@
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ ProcessWrapper processWrapper
) {
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mProcessWrapper = processWrapper;
}
@Override
@@ -109,6 +114,16 @@
final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
mSessionToInstanceId.put(type, instanceId);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
+
try {
if (DEBUG) {
Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
@@ -139,6 +154,14 @@
if (endSessionUiEvent != null) {
mUiEventLogger.log(endSessionUiEvent, instanceId);
}
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
mStatusBarManagerService.onSessionEnded(type, instanceId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to send onSessionEnded for session="
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index bda0069..e7c2a45 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,7 +17,6 @@
package com.android.systemui.media;
import android.annotation.Nullable;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -124,13 +123,9 @@
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
- Log.d(TAG, "play(token=" + token + ", uri=" + uri
- + ", uid=" + Binder.getCallingUid()
- + ") uriUserId=" + ContentProvider.getUserIdFromUri(uri)
- + " callingUserId=" + Binder.getCallingUserHandle().getIdentifier());
+ Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ + Binder.getCallingUid() + ")");
}
- enforceUriUserId(uri);
-
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -212,7 +207,6 @@
@Override
public String getTitle(Uri uri) {
- enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
@@ -245,25 +239,6 @@
}
throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
}
-
- /**
- * Must be called from the Binder calling thread.
- * Ensures caller is from the same userId as the content they're trying to access.
- * @param uri the URI to check
- * @throws SecurityException when non-system call or userId in uri differs from the
- * caller's userId
- */
- private void enforceUriUserId(Uri uri) throws SecurityException {
- final int uriUserId = ContentProvider.getUserIdFromUri(uri);
- final int callerUserId = Binder.getCallingUserHandle().getIdentifier();
- // for a non-system call, verify the URI to play belongs to the same user as the caller
- if (UserHandle.isApp(Binder.getCallingUid()) && uriUserId != callerUserId) {
- throw new SecurityException("Illegal access to uri=" + uri
- + " content associated with user=" + uriUserId
- + ", request originates from user=" + callerUserId);
- }
- }
-
};
private Context getContextForUser(UserHandle user) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 125f973..46c5c18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -662,13 +662,9 @@
@VisibleForTesting
internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
return scope.launch {
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }
- } else {
- keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter {
- it.transitionState == TransitionState.FINISHED
- }
- }
+ keyguardTransitionInteractor
+ .isFinishedIn(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+ .filter { it }
.collect {
showMediaCarousel()
updateHostVisibility()
@@ -849,8 +845,25 @@
commonViewModels.addAll(viewModels)
// Ensure we only show the needed UMOs in media carousel.
- val viewSet = viewModels.toHashSet()
- controllerByViewModel.filter { !viewSet.contains(it.key) }.forEach { onRemoved(it.key) }
+ val viewIds =
+ viewModels
+ .map { mediaCommonViewModel ->
+ when (mediaCommonViewModel) {
+ is MediaCommonViewModel.MediaControl ->
+ mediaCommonViewModel.instanceId.toString()
+ is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key
+ }
+ }
+ .toHashSet()
+ controllerByViewModel
+ .filter {
+ when (val viewModel = it.key) {
+ is MediaCommonViewModel.MediaControl ->
+ !viewIds.contains(viewModel.instanceId.toString())
+ is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key)
+ }
+ }
+ .forEach { onRemoved(it.key) }
}
private suspend fun getMediaLockScreenSetting(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt
similarity index 84%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt
index c92234c..a80bc09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionLog.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.mediaprojection
import javax.inject.Qualifier
+/** Logs for media projection related events. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+annotation class MediaProjectionLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
index 3489459..7fd77a9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediaprojection
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediaprojection.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaProjectionModule {
@Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaProjectionLog
+ fun provideMediaProjectionLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaProjection", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
index de300b2..82b4825 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
@@ -28,13 +28,25 @@
*
* @property hostPackage the package name of the app that is receiving the content of the media
* projection (aka which app the phone screen contents are being sent to).
+ * @property hostDeviceName the name of the other device that's receiving the content of the
+ * media projection. Null if the media projection is going to this same device (e.g. another
+ * app is recording the screen).
*/
- sealed class Projecting(open val hostPackage: String) : MediaProjectionState {
+ sealed class Projecting(
+ open val hostPackage: String,
+ open val hostDeviceName: String?,
+ ) : MediaProjectionState {
/** The entire screen is being projected. */
- data class EntireScreen(override val hostPackage: String) : Projecting(hostPackage)
+ data class EntireScreen(
+ override val hostPackage: String,
+ override val hostDeviceName: String? = null,
+ ) : Projecting(hostPackage, hostDeviceName)
/** Only a single task is being projected. */
- data class SingleTask(override val hostPackage: String, val task: RunningTaskInfo) :
- Projecting(hostPackage)
+ data class SingleTask(
+ override val hostPackage: String,
+ override val hostDeviceName: String?,
+ val task: RunningTaskInfo,
+ ) : Projecting(hostPackage, hostDeviceName)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 8a9adc7..5704e80 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -17,10 +17,10 @@
package com.android.systemui.mediaprojection.data.repository
import android.app.ActivityManager.RunningTaskInfo
+import android.hardware.display.DisplayManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.os.Handler
-import android.util.Log
import android.view.ContentRecordingSession
import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -28,6 +28,9 @@
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.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediaprojection.MediaProjectionLog
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
@@ -35,37 +38,49 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
class MediaProjectionManagerRepository
@Inject
constructor(
private val mediaProjectionManager: MediaProjectionManager,
+ private val displayManager: DisplayManager,
@Main private val handler: Handler,
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val tasksRepository: TasksRepository,
private val mediaProjectionServiceHelper: MediaProjectionServiceHelper,
+ @MediaProjectionLog private val logger: LogBuffer,
) : MediaProjectionRepository {
override suspend fun switchProjectedTask(task: RunningTaskInfo) {
withContext(backgroundDispatcher) {
if (mediaProjectionServiceHelper.updateTaskRecordingSession(task.token)) {
- Log.d(TAG, "Successfully switched projected task")
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Successfully switched projected task" })
} else {
- Log.d(TAG, "Failed to switch projected task")
+ logger.log(TAG, LogLevel.WARNING, {}, { "Failed to switch projected task" })
}
}
}
override suspend fun stopProjecting() {
- withContext(backgroundDispatcher) { mediaProjectionManager.stopActiveProjection() }
+ withContext(backgroundDispatcher) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "Requesting MediaProjectionManager#stopActiveProjection" },
+ )
+ mediaProjectionManager.stopActiveProjection()
+ }
}
override val mediaProjectionState: Flow<MediaProjectionState> =
@@ -73,28 +88,65 @@
val callback =
object : MediaProjectionManager.Callback() {
override fun onStart(info: MediaProjectionInfo?) {
- Log.d(TAG, "MediaProjectionManager.Callback#onStart")
- trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "MediaProjectionManager.Callback#onStart" },
+ )
+ trySendWithFailureLogging(CallbackEvent.OnStart, TAG)
}
override fun onStop(info: MediaProjectionInfo?) {
- Log.d(TAG, "MediaProjectionManager.Callback#onStop")
- trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "MediaProjectionManager.Callback#onStop" },
+ )
+ trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
}
override fun onRecordingSessionSet(
info: MediaProjectionInfo,
session: ContentRecordingSession?
) {
- Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
- launch {
- trySendWithFailureLogging(stateForSession(info, session), TAG)
- }
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = session.toString() },
+ { "MediaProjectionManager.Callback#onSessionStarted: $str1" },
+ )
+ trySendWithFailureLogging(
+ CallbackEvent.OnRecordingSessionSet(info, session),
+ TAG,
+ )
}
}
mediaProjectionManager.addCallback(callback, handler)
awaitClose { mediaProjectionManager.removeCallback(callback) }
}
+ // When we get an #onRecordingSessionSet event, we need to do some work in the
+ // background before emitting the right state value. But when we get an #onStop
+ // event, we immediately know what state value to emit.
+ //
+ // Without `mapLatest`, this could be a problem if an #onRecordingSessionSet event
+ // comes in and then an #onStop event comes in shortly afterwards (b/352483752):
+ // 1. #onRecordingSessionSet -> start some work in the background
+ // 2. #onStop -> immediately emit "Not Projecting"
+ // 3. onRecordingSessionSet work finishes -> emit "Projecting"
+ //
+ // At step 3, we *shouldn't* emit "Projecting" because #onStop was the last callback
+ // event we received, so we should be "Not Projecting". This `mapLatest` ensures
+ // that if an #onStop event comes in, we cancel any ongoing work for
+ // #onRecordingSessionSet and we don't emit "Projecting".
+ .mapLatest {
+ when (it) {
+ is CallbackEvent.OnStart,
+ is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
+ is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
+ }
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.Lazily,
@@ -110,14 +162,36 @@
}
val hostPackage = info.packageName
+ val hostDeviceName =
+ withContext(backgroundDispatcher) {
+ // If the projection is to a different device, then the session's display ID should
+ // identify the display associated with that different device.
+ displayManager.getDisplay(session.virtualDisplayId)?.name
+ }
+
if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
- return MediaProjectionState.Projecting.EntireScreen(hostPackage)
+ return MediaProjectionState.Projecting.EntireScreen(hostPackage, hostDeviceName)
}
val matchingTask =
tasksRepository.findRunningTaskFromWindowContainerToken(
checkNotNull(session.tokenToRecord)
- ) ?: return MediaProjectionState.Projecting.EntireScreen(hostPackage)
- return MediaProjectionState.Projecting.SingleTask(hostPackage, matchingTask)
+ ) ?: return MediaProjectionState.Projecting.EntireScreen(hostPackage, hostDeviceName)
+ return MediaProjectionState.Projecting.SingleTask(hostPackage, hostDeviceName, matchingTask)
+ }
+
+ /**
+ * Translates [MediaProjectionManager.Callback] events into objects so that we always maintain
+ * the correct callback ordering.
+ */
+ sealed interface CallbackEvent {
+ data object OnStart : CallbackEvent
+
+ data object OnStop : CallbackEvent
+
+ data class OnRecordingSessionSet(
+ val info: MediaProjectionInfo,
+ val session: ContentRecordingSession?,
+ ) : CallbackEvent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
similarity index 84%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
index c92234c..16bf0ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.mediarouter
import javax.inject.Qualifier
+/** Logs for events related to MediaRouter APIs. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+annotation class MediaRouterLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
index c07e3a0..df5dae4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediarouter
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
import com.android.systemui.mediarouter.data.repository.MediaRouterRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaRouterModule {
@Binds fun mediaRouterRepository(impl: MediaRouterRepositoryImpl): MediaRouterRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaRouterLog
+ fun provideMediaRouterLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaRouter", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index 998d76c..debb667 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediarouter.MediaRouterLog
import com.android.systemui.statusbar.policy.CastController
import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -26,6 +29,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** A repository for data coming from MediaRouter APIs. */
@@ -43,23 +49,29 @@
constructor(
@Application private val scope: CoroutineScope,
private val castController: CastController,
+ @MediaRouterLog private val logger: LogBuffer,
) : MediaRouterRepository {
override val castDevices: StateFlow<List<CastDevice>> =
conflatedCallbackFlow {
- val callback =
- CastController.Callback {
- val mediaRouterCastDevices =
- castController.castDevices.filter {
- it.origin == CastDevice.CastOrigin.MediaRouter
- }
- trySend(mediaRouterCastDevices)
- }
+ val callback = CastController.Callback { trySend(castController.castDevices) }
castController.addCallback(callback)
awaitClose { castController.removeCallback(callback) }
}
+ // The CastController.Callback is pretty noisy and sends the same values multiple times
+ // in a row, so use a distinctUntilChanged before logging.
+ .distinctUntilChanged()
+ .onEach { allDevices ->
+ val logString = allDevices.map { it.shortLogString }.toString()
+ logger.log(TAG, LogLevel.INFO, { str1 = logString }, { "All cast devices: $str1" })
+ }
+ .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override fun stopCasting(device: CastDevice) {
castController.stopCasting(device)
}
+
+ companion object {
+ private const val TAG = "MediaRouterRepo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 18358a7..d8c13b6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -33,6 +33,7 @@
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
@@ -83,7 +84,7 @@
class BackPanelController
internal constructor(
context: Context,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
private val mainHandler: Handler,
private val systemClock: SystemClock,
@@ -102,7 +103,7 @@
class Factory
@Inject
constructor(
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
@BackPanelUiThread private val uiThreadContext: UiThreadContext,
private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index e832abb..afdfa59 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -40,6 +40,8 @@
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.systemui.shared.rotation.RotationButtonController.DEBUG_ROTATION;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -48,8 +50,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
-import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
@@ -97,6 +97,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
@@ -117,17 +118,17 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.NavBarHelper;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.DeadZone;
import com.android.systemui.navigationbar.views.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.views.buttons.NavBarButtonClickLogger;
import com.android.systemui.navigationbar.views.buttons.NavbarOrientationTrackingLogger;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
@@ -1348,6 +1349,9 @@
ButtonDispatcher imeSwitcherButton = mView.getImeSwitchButton();
imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
+ if (Flags.imeSwitcherRevamp()) {
+ imeSwitcherButton.setOnLongClickListener(this::onImeSwitcherLongClick);
+ }
updateScreenPinningGestures();
}
@@ -1501,12 +1505,29 @@
mCommandQueue.toggleRecentApps();
}
- private void onImeSwitcherClick(View v) {
+ @VisibleForTesting
+ void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
+ if (Flags.imeSwitcherRevamp()) {
+ mInputMethodManager.onImeSwitchButtonClickFromSystem(mDisplayId);
+ } else {
+ mInputMethodManager.showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mDisplayId);
+ }
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ }
+
+ @VisibleForTesting
+ boolean onImeSwitcherLongClick(View v) {
+ if (!Flags.imeSwitcherRevamp()) {
+ return false;
+ }
mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
- mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
- };
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ return true;
+ }
private boolean onLongPressBackHome(View v) {
return onLongPressNavigationButtons(v, R.id.back, R.id.home);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 0f36097..1dbd500 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -19,7 +19,6 @@
import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
@@ -38,7 +37,6 @@
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
-import android.media.permission.SafeCloseable;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.AttributeSet;
@@ -61,19 +59,18 @@
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.ContextualButton;
import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
import com.android.systemui.navigationbar.views.buttons.DeadZone;
import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
@@ -181,7 +178,6 @@
private boolean mOverviewProxyEnabled;
private boolean mShowSwipeUpUi;
private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
- private SafeCloseable mViewCaptureCloseable;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -1082,10 +1078,6 @@
}
updateNavButtonIcons();
- if (enableViewCaptureTracing()) {
- mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
- .startCapture(getRootView(), ".NavigationBarView");
- }
}
@Override
@@ -1098,9 +1090,6 @@
mFloatingRotationButton.hide();
mRotationButtonController.unregisterListeners();
}
- if (mViewCaptureCloseable != null) {
- mViewCaptureCloseable.close();
- }
}
void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index 1e85d6c..133d14d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -112,6 +112,9 @@
@UiEvent(doc = "The overview button was long-pressed in the navigation bar.")
NAVBAR_OVERVIEW_BUTTON_LONGPRESS(538),
+ @UiEvent(doc = "The ime switcher button was long-pressed in the navigation bar.")
+ NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS(1799),
+
NONE(0); // an event we should not log
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
index d8c96dd..eadbffe 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
@@ -41,6 +41,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.SharedPreferencesHelper;
@@ -67,6 +69,7 @@
private final UserHandle mUserHandle;
private final PackageManager mPackageManager;
private final IPeopleManager mIPeopleManager;
+ @Nullable
private final AppWidgetManager mAppWidgetManager;
/**
@@ -404,6 +407,9 @@
private List<String> getExistingWidgetsForUser(int userId) {
List<String> existingWidgets = new ArrayList<>();
+ if (mAppWidgetManager == null) {
+ return existingWidgets;
+ }
int[] ids = mAppWidgetManager.getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
for (int id : ids) {
@@ -491,7 +497,11 @@
/** Sends a broadcast to update the existing Conversation widgets. */
public static void updateWidgets(Context context) {
- int[] widgetIds = AppWidgetManager.getInstance(context)
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ if (manager == null) {
+ return;
+ }
+ int[] widgetIds = manager
.getAppWidgetIds(new ComponentName(context, PeopleSpaceWidgetProvider.class));
if (DEBUG) {
for (int id : widgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 0a880293..b0de80c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -140,7 +140,7 @@
private final Object mLock = new Object();
private final Context mContext;
private LauncherApps mLauncherApps;
- private AppWidgetManager mAppWidgetManager;
+ private Optional<AppWidgetManager> mAppWidgetManagerOptional;
private IPeopleManager mIPeopleManager;
private SharedPreferences mSharedPrefs;
private PeopleManager mPeopleManager;
@@ -183,8 +183,9 @@
};
@Inject
- public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
- CommonNotifCollection notifCollection,
+ public PeopleSpaceWidgetManager(Context context,
+ Optional<AppWidgetManager> appWidgetManagerOptional,
+ LauncherApps launcherApps, CommonNotifCollection notifCollection,
PackageManager packageManager, Optional<Bubbles> bubblesOptional,
UserManager userManager, NotificationManager notificationManager,
BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
@@ -192,7 +193,7 @@
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
+ mAppWidgetManagerOptional = appWidgetManagerOptional;
mIPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
mLauncherApps = launcherApps;
@@ -266,14 +267,14 @@
*/
@VisibleForTesting
PeopleSpaceWidgetManager(Context context,
- AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
+ Optional<AppWidgetManager> appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
CommonNotifCollection notifCollection, PackageManager packageManager,
Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
INotificationManager iNotificationManager, NotificationManager notificationManager,
@Background Executor executor, UserTracker userTracker) {
mContext = context;
- mAppWidgetManager = appWidgetManager;
+ mAppWidgetManagerOptional = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
@@ -337,6 +338,10 @@
/** Updates the current widget view with provided {@link PeopleSpaceTile}. */
private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
@@ -349,7 +354,7 @@
// Tell the AppWidgetManager to perform an update on the current app widget.
if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
- mAppWidgetManager.updateAppWidget(appWidgetId, views);
+ mAppWidgetManagerOptional.get().updateAppWidget(appWidgetId, views);
}
/** Updates tile in app widget options and the current view. */
@@ -362,13 +367,17 @@
/** Updates tile in app widget options and the current view. */
public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
if (tile == null) {
Log.w(TAG, "Storing null tile for widget " + appWidgetId);
}
synchronized (mTiles) {
mTiles.put(appWidgetId, tile);
}
- Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
+ Bundle options = mAppWidgetManagerOptional.get().getAppWidgetOptions(appWidgetId);
updateAppWidgetViews(appWidgetId, tile, options);
}
@@ -484,6 +493,10 @@
private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction action,
Collection<NotificationEntry> notifications) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -491,7 +504,7 @@
if (DEBUG) Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
return;
}
- int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
);
if (widgetIds.length == 0) {
@@ -807,10 +820,14 @@
UserHandle user,
NotificationChannel channel,
int modificationType) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
if (channel.isConversation()) {
mBgExecutor.execute(() -> {
if (mUserManager.isUserUnlocked(user)) {
- updateWidgets(mAppWidgetManager.getAppWidgetIds(
+ updateWidgets(mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
));
}
@@ -829,13 +846,18 @@
// learning about the widget. If so, the widget adder should have populated options with
// PeopleTileKey arguments.
if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
if (PeopleTileKey.isValid(optionsKey)) {
if (DEBUG) {
Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
+ optionsKey.getShortcutId());
}
- AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId);
+ AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManagerOptional.get(),
+ appWidgetId);
addNewWidget(appWidgetId, optionsKey);
}
// Update views for new widget dimensions.
@@ -1004,6 +1026,10 @@
public boolean requestPinAppWidget(ShortcutInfo shortcutInfo, Bundle options) {
if (DEBUG) Log.d(TAG, "Requesting pin widget, shortcutId: " + shortcutInfo.getId());
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return false;
+ }
+
RemoteViews widgetPreview = getPreview(shortcutInfo.getId(),
shortcutInfo.getUserHandle(), shortcutInfo.getPackage(), options);
if (widgetPreview == null) {
@@ -1017,7 +1043,8 @@
PeopleSpaceWidgetPinnedReceiver.getPendingIntent(mContext, shortcutInfo);
ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
- return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback);
+ return mAppWidgetManagerOptional.get().requestPinAppWidget(componentName, extras,
+ successCallback);
}
/** Returns a list of map entries corresponding to user's priority conversations. */
@@ -1104,7 +1131,11 @@
/** Updates any app widget to the current state, triggered by a broadcast update. */
@VisibleForTesting
void updateWidgetsFromBroadcastInBackground(String entryPoint) {
- int[] appWidgetIds = mAppWidgetManager.getAppWidgetIds(
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
+ int[] appWidgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
if (appWidgetIds == null) {
return;
@@ -1272,13 +1303,17 @@
remapSharedFile(widgets);
remapFollowupFile(widgets);
- int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
+ int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
Bundle b = new Bundle();
b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
for (int id : widgetIds) {
if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
- mAppWidgetManager.updateAppWidgetOptions(id, b);
+ mAppWidgetManagerOptional.get().updateAppWidgetOptions(id, b);
}
updateWidgets(widgetIds);
@@ -1437,14 +1472,15 @@
@VisibleForTesting
void updateGeneratedPreviewForUser(UserHandle user) {
if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
- || !mUserManager.isUserUnlocked(user)) {
+ || !mUserManager.isUserUnlocked(user) || mAppWidgetManagerOptional.isEmpty()) {
return;
}
// The widget provider may be disabled on SystemUI implementers, e.g. TvSystemUI.
ComponentName provider = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
- List<AppWidgetProviderInfo> infos = mAppWidgetManager.getInstalledProvidersForPackage(
- mContext.getPackageName(), user);
+ List<AppWidgetProviderInfo> infos =
+ mAppWidgetManagerOptional.get().getInstalledProvidersForPackage(
+ mContext.getPackageName(), user);
if (infos.stream().noneMatch(info -> info.provider.equals(provider))) {
return;
}
@@ -1452,7 +1488,7 @@
if (DEBUG) {
Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
}
- boolean success = mAppWidgetManager.setWidgetPreview(
+ boolean success = mAppWidgetManagerOptional.get().setWidgetPreview(
provider, WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
new RemoteViews(mContext.getPackageName(),
R.layout.people_space_placeholder_layout));
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index b4cc196..294d0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.process;
+import android.app.ActivityManager;
import android.os.Process;
import android.os.UserHandle;
@@ -37,6 +38,13 @@
}
/**
+ * Returns {@code true} if the foreground user is running the current process.
+ */
+ public boolean isForegroundUser() {
+ return ActivityManager.getCurrentUser() == myUserHandle().getIdentifier();
+ }
+
+ /**
* Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
*
* This should not be used to get the "current" user. This information only applies to the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 10c8e53..cf1dca3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import android.content.res.Resources;
+
import com.android.systemui.statusbar.policy.CallbackController;
public interface ReduceBrightColorsController extends
@@ -27,6 +29,14 @@
/** Sets the activation state of Reduce Bright Colors */
void setReduceBrightColorsActivated(boolean activated);
+ /** Sets whether Reduce Bright Colors is enabled */
+ void setReduceBrightColorsFeatureAvailable(boolean enabled);
+
+ /** Gets whether Reduce Bright Colors is enabled */
+ boolean isReduceBrightColorsFeatureAvailable();
+
+ /** Gets whether Reduce Bright Colors is being transitioned to Even Dimmer */
+ boolean isInUpgradeMode(Resources resources);
/**
* Listener invoked whenever the Reduce Bright Colors settings are changed.
*/
@@ -38,5 +48,12 @@
*/
default void onActivated(boolean activated) {
}
+ /**
+ * Listener invoked when the feature enabled state changes.
+ *
+ * @param enabled {@code true} if Reduce Bright Colors feature is enabled.
+ */
+ default void onFeatureEnabledChanged(boolean enabled) {
+ }
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index 846d63f..4d6cf78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -19,6 +19,7 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
@@ -47,6 +48,7 @@
private final ContentObserver mContentObserver;
private final SecureSettings mSecureSettings;
private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
+ private boolean mAvailable = true;
@Inject
public ReduceBrightColorsControllerImpl(UserTracker userTracker,
@@ -75,12 +77,20 @@
mCurrentUserTrackerCallback = new UserTracker.Callback() {
@Override
public void onUserChanged(int newUser, Context userContext) {
+ mAvailable = true;
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
}
}
}
@@ -94,9 +104,15 @@
if (!mListeners.contains(listener)) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
}
}
}
@@ -106,7 +122,11 @@
public void removeCallback(@androidx.annotation.NonNull Listener listener) {
synchronized (mListeners) {
if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ }
}
}
}
@@ -121,10 +141,36 @@
mManager.setReduceBrightColorsActivated(activated);
}
+ @Override
+ public void setReduceBrightColorsFeatureAvailable(boolean enabled) {
+ mAvailable = enabled;
+ dispatchOnEnabledChanged(enabled);
+ mAvailable = true;
+ }
+
+ @Override
+ public boolean isReduceBrightColorsFeatureAvailable() {
+ return mAvailable;
+ }
+
+ @Override
+ public boolean isInUpgradeMode(Resources resources) {
+ return com.android.server.display.feature.flags.Flags.evenDimmer()
+ && resources.getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ }
+
private void dispatchOnActivated(boolean activated) {
ArrayList<Listener> copy = new ArrayList<>(mListeners);
for (Listener l : copy) {
l.onActivated(activated);
}
}
+
+ private void dispatchOnEnabledChanged(boolean enabled) {
+ ArrayList<Listener> copy = new ArrayList<>(mListeners);
+ for (Listener l : copy) {
+ l.onFeatureEnabledChanged(enabled);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6092348..2287f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -19,6 +19,7 @@
import android.database.ContentObserver;
import android.os.Handler;
+import com.android.systemui.Flags;
import com.android.systemui.statusbar.policy.Listenable;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SettingsProxy;
@@ -74,10 +75,20 @@
mListening = listening;
if (listening) {
mObservedValue = getValueFromProvider();
- mSettingsProxy.registerContentObserverSync(
- mSettingsProxy.getUriFor(mSettingName), false, this);
+ if (Flags.qsRegisterSettingObserverOnBgThread()) {
+ mSettingsProxy.registerContentObserverAsync(
+ mSettingsProxy.getUriFor(mSettingName), false, this,
+ () -> mObservedValue = getValueFromProvider());
+ } else {
+ mSettingsProxy.registerContentObserverSync(
+ mSettingsProxy.getUriFor(mSettingName), false, this);
+ }
} else {
- mSettingsProxy.unregisterContentObserverSync(this);
+ if (Flags.qsRegisterSettingObserverOnBgThread()) {
+ mSettingsProxy.unregisterContentObserverAsync(this);
+ } else {
+ mSettingsProxy.unregisterContentObserverSync(this);
+ }
mObservedValue = mDefaultValue;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index a45d6f6..89f85ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -32,6 +32,7 @@
import androidx.annotation.Nullable;
+import com.android.server.display.feature.flags.Flags;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
@@ -116,6 +117,10 @@
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
possibleTiles.remove("cell");
possibleTiles.remove("wifi");
+ if (Flags.evenDimmer() && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled)) {
+ possibleTiles.remove("reduce_brightness");
+ }
for (String spec : possibleTiles) {
// Only add current and stock tiles that can be created from QSFactoryImpl.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index ff20707..78f4b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -19,18 +19,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
-import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
-import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
@@ -53,7 +52,10 @@
@Module
interface PanelsModule {
- @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+ @Binds
+ fun bindDefaultLargeTilesSpecsRepository(
+ impl: DefaultLargeTilesRepositoryImpl
+ ): DefaultLargeTilesRepository
@Binds
fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
@@ -87,16 +89,9 @@
companion object {
@Provides
@SysUISingleton
- @GridConsistencyLog
- fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
- return factory.create("GridConsistencyLog", 50)
- }
-
- @Provides
- @SysUISingleton
- @IconLabelVisibilityLog
- fun providesIconTileLabelVisibilityLog(factory: LogBufferFactory): LogBuffer {
- return factory.create("IconLabelVisibilityLog", 50)
+ @PanelsLog
+ fun providesPanelsLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("PanelsLog", 50)
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt
new file mode 100644
index 0000000..9a1671d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** Repository for the default set of [TileSpec] that should be displayed as large tiles. */
+interface DefaultLargeTilesRepository {
+ val defaultLargeTiles: Set<TileSpec>
+}
+
+@SysUISingleton
+class DefaultLargeTilesRepositoryImpl @Inject constructor() : DefaultLargeTilesRepository {
+ override val defaultLargeTiles =
+ setOf(
+ TileSpec.create("internet"),
+ TileSpec.create("bt"),
+ TileSpec.create("dnd"),
+ TileSpec.create("cast"),
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
deleted file mode 100644
index 095bdf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ /dev/null
@@ -1,44 +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.qs.panels.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-/** Repository for checking if a tile should be displayed as an icon. */
-interface IconTilesRepository {
- fun isIconTile(spec: TileSpec): Boolean
-}
-
-@SysUISingleton
-class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
-
- override fun isIconTile(spec: TileSpec): Boolean {
- return !LARGE_TILES.contains(spec)
- }
-
- companion object {
- private val LARGE_TILES =
- setOf(
- TileSpec.create("internet"),
- TileSpec.create("bt"),
- TileSpec.create("dnd"),
- TileSpec.create("cast"),
- )
- }
-}
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 f3e5b8f..971598d 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
@@ -20,6 +20,7 @@
import android.content.SharedPreferences
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -40,6 +41,7 @@
constructor(
private val userFileManager: UserFileManager,
private val userRepository: UserRepository,
+ private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
/** Whether to show the labels on icon tiles for the current user. */
@@ -51,6 +53,23 @@
}
.flowOn(backgroundDispatcher)
+ /** Set of [TileSpec] to display as large tiles for the current user. */
+ val largeTilesSpecs: Flow<Set<TileSpec>> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ val prefs = getSharedPrefs(userInfo.id)
+ prefs.observe().emitOnStart().map {
+ prefs
+ .getStringSet(
+ LARGE_TILES_SPECS_KEY,
+ defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet()
+ )
+ ?.map { TileSpec.create(it) }
+ ?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
+ }
+ }
+ .flowOn(backgroundDispatcher)
+
/** Sets for the current user whether to show the labels on icon tiles. */
fun setShowLabels(showLabels: Boolean) {
with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
@@ -58,6 +77,13 @@
}
}
+ /** Sets for the current user the set of [TileSpec] to display as large tiles. */
+ fun setLargeTilesSpecs(specs: Set<TileSpec>) {
+ with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+ edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ }
+ }
+
private fun getSharedPrefs(userId: Int): SharedPreferences {
return userFileManager.getSharedPreferences(
FILE_NAME,
@@ -68,6 +94,7 @@
companion object {
private const val ICON_LABELS_KEY = "show_icon_labels"
+ private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
const val FILE_NAME = "quick_settings_prefs"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
index ec9d151..86a29f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.data.repository
import android.content.res.Resources
+import com.android.server.display.feature.flags.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -32,10 +33,15 @@
/**
* List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec].
*/
+ val shouldRemoveRbcTile: Boolean =
+ Flags.evenDimmer() &&
+ resources.getBoolean(com.android.internal.R.bool.config_evenDimmerEnabled)
+
val stockTiles =
resources
.getString(R.string.quick_settings_tiles_stock)
.split(",")
+ .filterNot { shouldRemoveRbcTile && it.equals("reduce_brightness") }
.map(TileSpec::create)
.filterNot { it is TileSpec.Invalid }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
index 7732092..a2e7ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
@@ -20,8 +20,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -39,7 +39,7 @@
private val consistencyInteractors:
Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
- @GridConsistencyLog private val logBuffer: LogBuffer,
+ @PanelsLog private val logBuffer: LogBuffer,
@Application private val applicationScope: CoroutineScope,
) {
fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
index 6a899b0..fe40127 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
@@ -20,7 +20,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -33,7 +33,7 @@
@Inject
constructor(
private val preferencesInteractor: QSPreferencesInteractor,
- @IconLabelVisibilityLog private val logBuffer: LogBuffer,
+ @PanelsLog private val logBuffer: LogBuffer,
@Application scope: CoroutineScope,
) {
val showLabels: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 524ea8b..b18358c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -17,12 +17,54 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
-/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
+/** Interactor for retrieving the list of [TileSpec] to be displayed as icons and resizing icons. */
@SysUISingleton
-class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
- fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec)
+class IconTilesInteractor
+@Inject
+constructor(
+ repo: DefaultLargeTilesRepository,
+ private val preferencesInteractor: QSPreferencesInteractor,
+ @PanelsLog private val logBuffer: LogBuffer,
+ @Application private val applicationScope: CoroutineScope
+) {
+
+ private val largeTilesSpecs =
+ preferencesInteractor.largeTilesSpecs
+ .onEach { logChange(it) }
+ .stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
+
+ fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
+
+ fun resize(spec: TileSpec, toIcon: Boolean) {
+ if (toIcon) {
+ preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
+ } else {
+ preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
+ }
+ }
+
+ private fun logChange(specs: Set<TileSpec>) {
+ logBuffer.log(
+ LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG,
+ LogLevel.DEBUG,
+ { str1 = specs.toString() },
+ { "Large tiles change: $str1" }
+ )
+ }
+
+ private companion object {
+ const val LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG = "LargeTilesSpecsChange"
+ }
}
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 811be80..854e23f 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
@@ -18,14 +18,20 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@SysUISingleton
class QSPreferencesInteractor @Inject constructor(private val repo: QSPreferencesRepository) {
val showLabels: Flow<Boolean> = repo.showLabels
+ val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
fun setShowLabels(showLabels: Boolean) {
repo.setShowLabels(showLabels)
}
+
+ fun setLargeTilesSpecs(specs: Set<TileSpec>) {
+ repo.setLargeTilesSpecs(specs)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
deleted file mode 100644
index 884cde3..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
+++ /dev/null
@@ -1,24 +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.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class GridConsistencyLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
index c92234c..50cb386 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/PanelsLog.kt
@@ -18,7 +18,4 @@
import javax.inject.Qualifier
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class PanelsLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 007ec3a..782fb2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -48,6 +48,9 @@
val sourceSpec: MutableState<TileSpec?>,
private val listState: EditTileListState
) {
+ val dragInProgress: Boolean
+ get() = sourceSpec.value != null
+
/** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
fun currentPosition(): Int {
return sourceSpec.value?.let { listState.indexOf(it) } ?: -1
@@ -65,6 +68,12 @@
sourceSpec.value?.let { listState.move(it, targetSpec) }
}
+ fun movedOutOfBounds() {
+ // Removing the tiles from the current tile grid if it moves out of bounds. This clears
+ // the spacer and makes it apparent that dropping the tile at that point would remove it.
+ sourceSpec.value?.let { listState.removeFromCurrent(it) }
+ }
+
fun onDrop() {
sourceSpec.value = null
}
@@ -112,6 +121,42 @@
}
/**
+ * Registers a composable as a [DragAndDropTarget] to receive drop events. Use this outside the tile
+ * grid to catch out of bounds drops.
+ *
+ * @param dragAndDropState The [DragAndDropState] using the tiles list
+ * @param onDrop Action to be executed when a [TileSpec] is dropped on the composable
+ */
+@Composable
+fun Modifier.dragAndDropRemoveZone(
+ dragAndDropState: DragAndDropState,
+ onDrop: (TileSpec) -> Unit,
+): Modifier {
+ val target =
+ remember(dragAndDropState) {
+ object : DragAndDropTarget {
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return dragAndDropState.sourceSpec.value?.let {
+ onDrop(it)
+ dragAndDropState.onDrop()
+ true
+ } ?: false
+ }
+
+ override fun onEntered(event: DragAndDropEvent) {
+ dragAndDropState.movedOutOfBounds()
+ }
+ }
+ }
+ return dragAndDropTarget(
+ shouldStartDragAndDrop = { event ->
+ event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE)
+ },
+ target = target,
+ )
+}
+
+/**
* Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
* containers to catch drops outside of tiles.
*
@@ -128,6 +173,10 @@
val target =
remember(dragAndDropState) {
object : DragAndDropTarget {
+ override fun onEnded(event: DragAndDropEvent) {
+ dragAndDropState.onDrop()
+ }
+
override fun onDrop(event: DragAndDropEvent): Boolean {
return dragAndDropState.sourceSpec.value?.let {
onDrop(it, dragAndDropState.currentPosition())
@@ -149,11 +198,13 @@
fun Modifier.dragAndDropTileSource(
tileSpec: TileSpec,
onTap: (TileSpec) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState
): Modifier {
return dragAndDropSource {
detectTapGestures(
onTap = { onTap(tileSpec) },
+ onDoubleTap = { onDoubleTap(tileSpec) },
onLongPress = {
dragAndDropState.onStarted(tileSpec)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 482c498..34876c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -46,6 +46,18 @@
tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) }
}
+ /**
+ * Sets the [TileSpec] as a non-current tile. Use this when a tile is dragged out of the current
+ * tile grid.
+ */
+ fun removeFromCurrent(tileSpec: TileSpec) {
+ val fromIndex = indexOf(tileSpec)
+ if (fromIndex >= 0 && fromIndex < tiles.size) {
+ // Mark the moving tile as non-current
+ tiles[fromIndex] = tiles[fromIndex].copy(isCurrent = false)
+ }
+ }
+
fun indexOf(tileSpec: TileSpec): Int {
return tiles.indexOfFirst { it.tileSpec == tileSpec }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index ea97f0d..add830e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -76,14 +77,18 @@
onRemoveTile: (TileSpec) -> Unit,
) {
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
+ val isIcon: (TileSpec) -> Boolean by rememberUpdatedState { tileSpec ->
+ iconTilesViewModel.isIconTile(tileSpec)
+ }
DefaultEditTileGrid(
tiles = tiles,
- isIconOnly = iconTilesViewModel::isIconTile,
- columns = GridCells.Fixed(columns),
+ isIconOnly = isIcon,
+ columns = columns,
modifier = modifier,
onAddTile = onAddTile,
onRemoveTile = onRemoveTile,
+ onResize = iconTilesViewModel::resize,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 092ad44..6c84edd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -122,6 +122,9 @@
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
+ val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
+ viewModel.resize(tileSpec, !viewModel.isIconTile(tileSpec))
+ }
val largeTileHeight = tileHeight()
val iconTileHeight = tileHeight(showLabels)
val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
@@ -161,6 +164,7 @@
tilePadding = tilePadding,
onAdd = onAddTile,
onRemove = onRemoveTile,
+ onDoubleTap = onDoubleTap,
isIconOnly = viewModel::isIconTile,
columns = columns,
showLabels = showLabels,
@@ -173,6 +177,7 @@
tilePadding = tilePadding,
addTileToEnd = addTileToEnd,
onRemove = onRemoveTile,
+ onDoubleTap = onDoubleTap,
isIconOnly = viewModel::isIconTile,
showLabels = showLabels,
columns = columns,
@@ -203,6 +208,7 @@
tilePadding: Dp,
onAdd: (TileSpec, Int) -> Unit,
onRemove: (TileSpec) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
showLabels: Boolean,
columns: Int,
@@ -224,6 +230,7 @@
tiles = largeTiles,
clickAction = ClickAction.REMOVE,
onClick = onRemove,
+ onDoubleTap = onDoubleTap,
isIconOnly = { false },
dragAndDropState = dragAndDropState,
acceptDrops = { !isIconOnly(it) },
@@ -244,6 +251,7 @@
tiles = smallTiles,
clickAction = ClickAction.REMOVE,
onClick = onRemove,
+ onDoubleTap = onDoubleTap,
isIconOnly = { true },
showLabels = showLabels,
dragAndDropState = dragAndDropState,
@@ -263,6 +271,7 @@
tilePadding: Dp,
addTileToEnd: (TileSpec) -> Unit,
onRemove: (TileSpec) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
showLabels: Boolean,
columns: Int,
@@ -298,6 +307,7 @@
addTileToEnd,
isIconOnly,
dragAndDropState,
+ onDoubleTap = onDoubleTap,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -309,8 +319,9 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
showLabels = showLabels,
- dragAndDropState = dragAndDropState,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -322,8 +333,9 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
showLabels = showLabels,
- dragAndDropState = dragAndDropState,
acceptDrops = { true },
onDrop = onDrop,
)
@@ -360,11 +372,6 @@
}
}
- private fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
- val rows = (nTiles + columns - 1) / columns
- return ((tileHeight + padding) * rows) - padding
- }
-
/** Fill up the rest of the row if it's not complete. */
private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) {
if (nTiles % columns != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index 4a90102..3e48245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -100,10 +100,11 @@
DefaultEditTileGrid(
tiles = tiles,
isIconOnly = iconTilesViewModel::isIconTile,
- columns = GridCells.Fixed(columns),
+ columns = columns,
modifier = modifier,
onAddTile = onAddTile,
onRemoveTile = onRemoveTile,
+ onResize = iconTilesViewModel::resize,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 127ef84..bd7956d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,15 +22,20 @@
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
-import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
@@ -38,20 +43,31 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -81,6 +97,8 @@
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -270,76 +288,263 @@
fun DefaultEditTileGrid(
tiles: List<EditTileViewModel>,
isIconOnly: (TileSpec) -> Boolean,
- columns: GridCells,
+ columns: Int,
modifier: Modifier,
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
+ onResize: (TileSpec, Boolean) -> Unit,
) {
val currentListState = rememberEditListState(tiles)
val dragAndDropState = rememberDragAndDropState(currentListState)
-
val (currentTiles, otherTiles) = currentListState.tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) =
- otherTiles
- .filter { !dragAndDropState.isMoving(it.tileSpec) }
- .partition { it.appName == null }
+
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
-
val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
onAddTile(tileSpec, position)
}
- val onDropRemove: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
- onRemoveTile(tileSpec)
+ val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
+ onResize(tileSpec, !isIconOnly(tileSpec))
}
+ val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
- TileLazyGrid(
- modifier = modifier.dragAndDropTileList(dragAndDropState, { true }, onDropAdd),
- columns = columns
+ CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
+ ) {
+ AnimatedContent(
+ targetState = dragAndDropState.dragInProgress,
+ modifier = Modifier.wrapContentSize()
+ ) { dragIsInProgress ->
+ EditGridHeader(Modifier.dragAndDropRemoveZone(dragAndDropState, onRemoveTile)) {
+ if (dragIsInProgress) {
+ RemoveTileTarget()
+ } else {
+ Text(text = "Hold and drag to rearrange tiles.")
+ }
+ }
+ }
+
+ CurrentTilesGrid(
+ currentTiles,
+ columns,
+ tilePadding,
+ isIconOnly,
+ onRemoveTile,
+ onDoubleTap,
+ dragAndDropState,
+ onDropAdd,
+ )
+
+ // Hide available tiles when dragging
+ AnimatedVisibility(
+ visible = !dragAndDropState.dragInProgress,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize()
+ ) {
+ EditGridHeader { Text(text = "Hold and drag to add tiles.") }
+
+ AvailableTileGrid(
+ otherTiles,
+ columns,
+ tilePadding,
+ addTileToEnd,
+ dragAndDropState,
+ )
+ }
+ }
+
+ // Drop zone to remove tiles dragged out of the tile grid
+ Spacer(
+ modifier =
+ Modifier.fillMaxWidth()
+ .weight(1f)
+ .dragAndDropRemoveZone(dragAndDropState, onRemoveTile)
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditGridHeader(
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit
+) {
+ CompositionLocalProvider(
+ LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
) {
- // These Text are just placeholders to see the different sections. Not final UI.
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier.fillMaxWidth().height(TileDefaults.EditGridHeaderHeight)
+ ) {
+ content()
+ }
+ }
+}
- editTiles(
- currentTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- isIconOnly,
- indicatePosition = true,
- dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropAdd,
- )
+@Composable
+private fun RemoveTileTarget() {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ modifier =
+ Modifier.fillMaxHeight()
+ .border(1.dp, LocalContentColor.current, shape = TileDefaults.TileShape)
+ .padding(10.dp)
+ ) {
+ Icon(imageVector = Icons.Default.Clear, contentDescription = null)
+ Text(text = "Remove")
+ }
+}
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+@Composable
+private fun CurrentTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
+ shape = RoundedCornerShape(48.dp),
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+}
+@Composable
+private fun CurrentTilesGrid(
+ tiles: List<EditTileViewModel>,
+ columns: Int,
+ tilePadding: Dp,
+ isIconOnly: (TileSpec) -> Boolean,
+ onClick: (TileSpec) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+ onDrop: (TileSpec, Int) -> Unit
+) {
+ val tileHeight = tileHeight()
+ val currentRows =
+ remember(tiles) {
+ calculateRows(
+ tiles.map {
+ SizedTile(
+ it,
+ if (isIconOnly(it.tileSpec)) {
+ 1
+ } else {
+ 2
+ }
+ )
+ },
+ columns
+ )
+ }
+ val currentGridHeight = gridHeight(currentRows, tileHeight, tilePadding)
+ // Current tiles
+ CurrentTilesContainer {
+ TileLazyGrid(
+ modifier =
+ Modifier.height(currentGridHeight)
+ .dragAndDropTileList(dragAndDropState, { true }, onDrop),
+ columns = GridCells.Fixed(columns)
+ ) {
+ editTiles(
+ tiles,
+ ClickAction.REMOVE,
+ onClick,
+ isIconOnly,
+ dragAndDropState,
+ onDoubleTap = onDoubleTap,
+ indicatePosition = true,
+ acceptDrops = { true },
+ onDrop = onDrop,
+ )
+ }
+ }
+}
+
+@Composable
+private fun AvailableTileGrid(
+ tiles: List<EditTileViewModel>,
+ columns: Int,
+ tilePadding: Dp,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+) {
+ val (otherTilesStock, otherTilesCustom) =
+ tiles.filter { !dragAndDropState.isMoving(it.tileSpec) }.partition { it.appName == null }
+ val availableTileHeight = tileHeight(true)
+ val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
+
+ // Available tiles
+ TileLazyGrid(
+ modifier =
+ Modifier.height(availableGridHeight)
+ .dragAndDropTileList(dragAndDropState, { false }, { _, _ -> }),
+ columns = GridCells.Fixed(columns)
+ ) {
editTiles(
otherTilesStock,
ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
+ onClick,
+ isIconOnly = { true },
dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropRemove,
+ acceptDrops = { false },
+ showLabels = true,
)
-
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Custom tiles to add", color = Color.White)
- }
-
editTiles(
otherTilesCustom,
ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
+ onClick,
+ isIconOnly = { true },
dragAndDropState = dragAndDropState,
- acceptDrops = { true },
- onDrop = onDropRemove,
+ acceptDrops = { false },
+ showLabels = true,
)
}
}
+fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
+ val rows = (nTiles + columns - 1) / columns
+ return gridHeight(rows, tileHeight, padding)
+}
+
+fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
+ return ((tileHeight + padding) * rows) - padding
+}
+
+private fun calculateRows(tiles: List<SizedTile<EditTileViewModel>>, columns: Int): Int {
+ val row = TileRow<EditTileViewModel>(columns)
+ var count = 0
+
+ for (tile in tiles) {
+ if (row.maybeAddTile(tile)) {
+ if (row.isFull()) {
+ // Row is full, no need to stretch tiles
+ count += 1
+ row.clear()
+ }
+ } else {
+ count += 1
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ if (row.tiles.isNotEmpty()) {
+ count += 1
+ }
+ return count
+}
+
fun LazyGridScope.editTiles(
tiles: List<EditTileViewModel>,
clickAction: ClickAction,
@@ -347,7 +552,8 @@
isIconOnly: (TileSpec) -> Boolean,
dragAndDropState: DragAndDropState,
acceptDrops: (TileSpec) -> Boolean,
- onDrop: (TileSpec, Int) -> Unit,
+ onDoubleTap: (TileSpec) -> Unit = {},
+ onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
showLabels: Boolean = false,
indicatePosition: Boolean = false,
) {
@@ -390,6 +596,7 @@
.dragAndDropTileSource(
viewModel.tileSpec,
onClick,
+ onDoubleTap,
dragAndDropState,
)
)
@@ -456,7 +663,6 @@
animateToEnd: Boolean = false,
modifier: Modifier = Modifier,
) {
- Log.d("Fabian", "Recomposing tile icon")
val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
val context = LocalContext.current
val loadedDrawable =
@@ -527,6 +733,7 @@
private object TileDefaults {
val TileShape = CircleShape
val IconTileWithLabelHeight = 140.dp
+ val EditGridHeaderHeight = 60.dp
@Composable
fun activeTileColors(): TileColors =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 62bfc72..ef2c8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -151,12 +151,27 @@
* present, it will be moved to the new position.
*/
fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
- // Removing tile if it's already present to insert it at the new index.
- if (currentTilesInteractor.currentTilesSpecs.contains(tileSpec)) {
- removeTile(tileSpec)
+ val specs = currentTilesInteractor.currentTilesSpecs.toMutableList()
+ val currentPosition = specs.indexOf(tileSpec)
+
+ if (currentPosition != -1) {
+ // No operation needed if the element is already in the list at the right position
+ if (currentPosition == position) {
+ return
+ }
+ // Removing tile if it's present at a different position to insert it at the new index.
+ specs.removeAt(currentPosition)
}
- currentTilesInteractor.addTile(tileSpec, position)
+ if (position >= 0 && position < specs.size) {
+ specs.add(position, tileSpec)
+ } else {
+ specs.add(tileSpec)
+ }
+
+ // Setting the new tiles as one operation to avoid UI jank with tiles disappearing and
+ // reappearing
+ currentTilesInteractor.setTiles(specs)
}
/** Immediately removes [tileSpec] from the current tiles. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 117c85c..8d2d74a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -23,10 +23,14 @@
interface IconTilesViewModel {
fun isIconTile(spec: TileSpec): Boolean
+
+ fun resize(spec: TileSpec, toIcon: Boolean)
}
@SysUISingleton
class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
IconTilesViewModel {
override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
+
+ override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
index 9c1b857..4d823ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -38,10 +38,11 @@
@Inject
constructor(
controller: ReduceBrightColorsController,
- @Named(RBC_AVAILABLE) private val available: Boolean,
+ @Named(RBC_AVAILABLE) private var available: Boolean,
) :
CallbackControllerAutoAddable<
- ReduceBrightColorsController.Listener, ReduceBrightColorsController
+ ReduceBrightColorsController.Listener,
+ ReduceBrightColorsController
>(controller) {
override val spec: TileSpec
@@ -50,10 +51,16 @@
override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
return object : ReduceBrightColorsController.Listener {
override fun onActivated(activated: Boolean) {
- if (activated) {
+ if (activated && available) {
sendAdd()
}
}
+
+ override fun onFeatureEnabledChanged(enabled: Boolean) {
+ if (!enabled) {
+ available = false
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 44c846b..6b3dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -93,6 +93,7 @@
@VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_"
@VisibleForTesting internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
@VisibleForTesting internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
+ internal val EMPTY_RECT = Rect()
}
private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -386,6 +387,7 @@
// The launch animation of a long-press effect did not reset the long-press effect so
// we must do it here
resetLongPressEffectProperties()
+ longPressEffect.resetState()
}
val actualHeight =
if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
@@ -771,11 +773,14 @@
lastIconTint = icon.getColor(state)
// Long-press effects
+ longPressEffect?.qsTile?.state?.handlesLongClick = state.handlesLongClick
if (
state.handlesLongClick &&
longPressEffect?.initializeEffect(longPressEffectDuration) == true
) {
showRippleEffect = false
+ longPressEffect.qsTile?.state?.state = lastState // Store the tile's state
+ longPressEffect.resetState()
initializeLongPressProperties(measuredHeight, measuredWidth)
} else {
// Long-press effects might have been enabled before but the new state does not
@@ -906,12 +911,13 @@
}
override fun onActivityLaunchAnimationEnd() {
+ longPressEffect?.resetState()
if (longPressEffect != null && !haveLongPressPropertiesBeenReset) {
resetLongPressEffectProperties()
}
}
- fun prepareForLaunch() {
+ private fun prepareForLaunch() {
val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
val deltaH = finalLongPressProperties?.height?.minus(startingHeight)?.toInt() ?: 0
@@ -922,7 +928,12 @@
paddingForLaunch.bottom = deltaH / 2
}
- override fun getPaddingForLaunchAnimation(): Rect = paddingForLaunch
+ override fun getPaddingForLaunchAnimation(): Rect =
+ if (longPressEffect?.state == QSLongPressEffect.State.LONG_CLICKED) {
+ paddingForLaunch
+ } else {
+ EMPTY_RECT
+ }
fun updateLongPressEffectProperties(effectProgress: Float) {
if (!isLongClickable || longPressEffect == null) return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index f34389e..53594bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -20,12 +20,14 @@
/** Return the subtitle resource Id of the given tile. */
object SubtitleArrayMapping {
private val subtitleIdsMap: HashMap<String, Int> = HashMap()
+
init {
subtitleIdsMap["internet"] = R.array.tile_states_internet
subtitleIdsMap["wifi"] = R.array.tile_states_wifi
subtitleIdsMap["cell"] = R.array.tile_states_cell
subtitleIdsMap["battery"] = R.array.tile_states_battery
subtitleIdsMap["dnd"] = R.array.tile_states_dnd
+ subtitleIdsMap["modes"] = R.array.tile_states_modes
subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight
subtitleIdsMap["rotation"] = R.array.tile_states_rotation
subtitleIdsMap["bt"] = R.array.tile_states_bt
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 3472352..af5b311 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -48,12 +48,13 @@
/** Quick settings tile: Reduce Bright Colors **/
public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
- implements ReduceBrightColorsController.Listener{
+ implements ReduceBrightColorsController.Listener {
public static final String TILE_SPEC = "reduce_brightness";
- private final boolean mIsAvailable;
+ private boolean mIsAvailable;
private final ReduceBrightColorsController mReduceBrightColorsController;
private boolean mIsListening;
+ private final boolean mInUpgradeMode;
@Inject
public ReduceBrightColorsTile(
@@ -73,9 +74,11 @@
statusBarStateController, activityStarter, qsLogger);
mReduceBrightColorsController = reduceBrightColorsController;
mReduceBrightColorsController.observe(getLifecycle(), this);
- mIsAvailable = isAvailable;
+ mInUpgradeMode = reduceBrightColorsController.isInUpgradeMode(mContext.getResources());
+ mIsAvailable = isAvailable || mInUpgradeMode;
}
+
@Override
public boolean isAvailable() {
return mIsAvailable;
@@ -93,12 +96,28 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS);
+ return goToEvenDimmer() ? new Intent(Settings.ACTION_DISPLAY_SETTINGS) : new Intent(
+ Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS);
+ }
+
+ private boolean goToEvenDimmer() {
+ if (mInUpgradeMode) {
+ mHost.removeTile(getTileSpec());
+ mIsAvailable = false;
+ return true;
+ }
+ return false;
}
@Override
protected void handleClick(@Nullable Expandable expandable) {
- mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value);
+
+ if (goToEvenDimmer()) {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Settings.ACTION_DISPLAY_SETTINGS), 0);
+ } else {
+ mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value);
+ }
}
@Override
@@ -127,4 +146,9 @@
public void onActivated(boolean activated) {
refreshState();
}
+
+ @Override
+ public void onFeatureEnabledChanged(boolean enabled) {
+ refreshState();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index df7430a..158eb6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -65,6 +65,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -184,7 +185,7 @@
private GlobalSettings mGlobalSettings;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
@@ -246,7 +247,7 @@
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
- WindowManager windowManager, ToastFactory toastFactory,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, ToastFactory toastFactory,
@Background Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
@@ -278,7 +279,7 @@
mAccessPointController = accessPointController;
mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
mConnectivityManagerNetworkCallback = new DataConnectivityListener();
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
mSecondarySignalDrawable = new SignalDrawable(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c971f54..edc49cac2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -183,9 +183,9 @@
Context context,
InternetDialogManager internetDialogManager,
InternetDialogController internetDialogController,
- @Assisted(ABOVE_STATUS_BAR) boolean canConfigMobileData,
- @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigWifi,
- @Assisted(CAN_CONFIG_WIFI) boolean aboveStatusBar,
+ @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData,
+ @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi,
+ @Assisted(ABOVE_STATUS_BAR) boolean aboveStatusBar,
@Assisted CoroutineScope coroutineScope,
UiEventLogger uiEventLogger,
DialogTransitionAnimator dialogTransitionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
new file mode 100644
index 0000000..930109a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.qs.tiles.impl.modes.domain.interactor
+
+import android.app.Flags
+import android.os.UserHandle
+import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
+ QSTileDataInteractor<ModesTileModel> {
+ private val zenModeActive =
+ zenModeRepository.modes
+ .map { modes -> modes.any { mode -> mode.isActive } }
+ .distinctUntilChanged()
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<ModesTileModel> {
+ return zenModeActive.map { ModesTileModel(isActivated = it) }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
new file mode 100644
index 0000000..e2fea84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.tiles.impl.modes.domain.interactor
+
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+class ModesTileUserActionInteractor @Inject constructor() :
+ QSTileUserActionInteractor<ModesTileModel> {
+ override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // TODO(b/346519570) open dialog
+ }
+ is QSTileUserAction.LongClick -> {
+ // TODO(b/346519570) open settings
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index c92234c..e44413a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -14,11 +14,5 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
-
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+package com.android.systemui.qs.tiles.impl.modes.domain.model
+data class ModesTileModel(val isActivated: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
new file mode 100644
index 0000000..07b393e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.modes.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class ModesTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ModesTileModel> {
+ override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ val iconRes =
+ if (data.isActivated) {
+ R.drawable.qs_dnd_icon_on
+ } else {
+ R.drawable.qs_dnd_icon_off
+ }
+ val icon = Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+ this.icon = { icon }
+ if (data.isActivated) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = "Some modes enabled idk" // TODO(b/346519570)
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = "Off" // TODO(b/346519570)
+ }
+ contentDescription = label
+ supportedActions =
+ setOf(
+ QSTileState.UserAction.CLICK,
+ QSTileState.UserAction.LONG_CLICK,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
index 98fd561..00b1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
@@ -23,13 +23,13 @@
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.util.kotlin.isAvailable
import com.android.systemui.util.kotlin.isEnabled
import javax.inject.Inject
import javax.inject.Named
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -52,5 +52,7 @@
.map { ReduceBrightColorsTileModel(it) }
.flowOn(bgCoroutineContext)
}
- override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable)
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ reduceBrightColorsController.isAvailable()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
index 14dbe0e..ed5e4fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
import android.content.Intent
+import android.content.res.Resources
import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.ReduceBrightColorsController
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
@@ -30,19 +32,40 @@
class ReduceBrightColorsTileUserActionInteractor
@Inject
constructor(
+ @Main private val resources: Resources,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
private val reduceBrightColorsController: ReduceBrightColorsController,
) : QSTileUserActionInteractor<ReduceBrightColorsTileModel> {
+ val isInUpgradeMode: Boolean = reduceBrightColorsController.isInUpgradeMode(resources)
+
override suspend fun handleInput(input: QSTileInput<ReduceBrightColorsTileModel>): Unit =
with(input) {
when (action) {
is QSTileUserAction.Click -> {
+ if (isInUpgradeMode) {
+ reduceBrightColorsController.setReduceBrightColorsFeatureAvailable(false)
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ Intent(Settings.ACTION_DISPLAY_SETTINGS)
+ )
+ // TODO(b/349458355): show dialog
+ return@with
+ }
reduceBrightColorsController.setReduceBrightColorsActivated(
!input.data.isEnabled
)
}
is QSTileUserAction.LongClick -> {
+ if (isInUpgradeMode) {
+ reduceBrightColorsController.setReduceBrightColorsFeatureAvailable(false)
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ Intent(Settings.ACTION_DISPLAY_SETTINGS)
+ )
+ // TODO(b/349458355): show dialog
+ return@with
+ }
qsTileIntentUserActionHandler.handle(
action.expandable,
Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b3624ad..371707d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -72,6 +72,7 @@
import android.view.MotionEvent;
import android.view.Surface;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
@@ -302,10 +303,29 @@
public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using the default display for now since
// Launcher/Taskbar isn't display aware.
+ if (Flags.imeSwitcherRevamp()) {
+ mContext.getSystemService(InputMethodManager.class)
+ .onImeSwitchButtonClickFromSystem(mDisplayTracker.getDefaultDisplayId());
+ } else {
+ mContext.getSystemService(InputMethodManager.class)
+ .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
+ mDisplayTracker.getDefaultDisplayId());
+ }
+ mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ }
+
+ @Override
+ public void onImeSwitcherLongPress() {
+ if (!Flags.imeSwitcherRevamp()) {
+ return;
+ }
+ // TODO(b/204901476) We're intentionally using the default display for now since
+ // Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
.showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
mDisplayTracker.getDefaultDisplayId());
- mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ mUiEventLogger.log(
+ KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1f4820a..8711e88 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -665,7 +665,7 @@
keyguardEnabledInteractor.isKeyguardEnabled
.sample(
combine(
- deviceEntryInteractor.isInLockdown,
+ deviceUnlockedInteractor.isInLockdown,
deviceEntryInteractor.isDeviceEntered,
::Pair,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 3dc2070..46c5861 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -27,7 +27,6 @@
import android.os.CountDownTimer;
import android.os.Process;
import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -60,8 +59,6 @@
@SysUISingleton
public class RecordingController
implements CallbackController<RecordingController.RecordingStateChangeCallback> {
- private static final String TAG = "RecordingController";
-
private boolean mIsStarting;
private boolean mIsRecording;
private PendingIntent mStopIntent;
@@ -71,6 +68,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final FeatureFlags mFlags;
private final UserTracker mUserTracker;
+ private final RecordingControllerLogger mRecordingControllerLogger;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
@@ -102,9 +100,10 @@
if (intent != null && INTENT_UPDATE_STATE.equals(intent.getAction())) {
if (intent.hasExtra(EXTRA_STATE)) {
boolean state = intent.getBooleanExtra(EXTRA_STATE, false);
+ mRecordingControllerLogger.logIntentStateUpdated(state);
updateState(state);
} else {
- Log.e(TAG, "Received update intent with no state");
+ mRecordingControllerLogger.logIntentMissingState();
}
}
}
@@ -120,6 +119,7 @@
FeatureFlags flags,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker,
+ RecordingControllerLogger recordingControllerLogger,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
@@ -130,6 +130,7 @@
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
+ mRecordingControllerLogger = recordingControllerLogger;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
mScreenRecordDialogFactory = screenRecordDialogFactory;
@@ -212,9 +213,9 @@
IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
UserHandle.ALL);
- Log.d(TAG, "sent start intent");
+ mRecordingControllerLogger.logSentStartIntent();
} catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+ mRecordingControllerLogger.logPendingIntentCancelled(e);
}
}
};
@@ -227,9 +228,10 @@
*/
public void cancelCountdown() {
if (mCountDownTimer != null) {
+ mRecordingControllerLogger.logCountdownCancelled();
mCountDownTimer.cancel();
} else {
- Log.e(TAG, "Timer was null");
+ mRecordingControllerLogger.logCountdownCancelErrorNoTimer();
}
mIsStarting = false;
@@ -260,13 +262,14 @@
public void stopRecording() {
try {
if (mStopIntent != null) {
+ mRecordingControllerLogger.logRecordingStopped();
mStopIntent.send(mInteractiveBroadcastOption);
} else {
- Log.e(TAG, "Stop intent was null");
+ mRecordingControllerLogger.logRecordingStopErrorNoStopIntent();
}
updateState(false);
} catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Error stopping: " + e.getMessage());
+ mRecordingControllerLogger.logRecordingStopError(e);
}
}
@@ -275,6 +278,7 @@
* @param isRecording
*/
public synchronized void updateState(boolean isRecording) {
+ mRecordingControllerLogger.logStateUpdated(isRecording);
if (!isRecording && mIsRecording) {
// Unregister receivers if we have stopped recording
mUserTracker.removeCallback(mUserChangedCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt
index c92234c..dd2baef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLog.kt
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.screenrecord
import javax.inject.Qualifier
+/**
+ * Logs for screen record events. See [com.android.systemui.screenrecord.RecordingController] and
+ * [com.android.systemui.screenrecord.RecordingControllerLogger].
+ */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+annotation class RecordingControllerLog
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt
new file mode 100644
index 0000000..e16c010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingControllerLogger.kt
@@ -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.systemui.screenrecord
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Helper class for logging events to [RecordingControllerLog] from Java. */
+@SysUISingleton
+class RecordingControllerLogger
+@Inject
+constructor(
+ @RecordingControllerLog private val logger: LogBuffer,
+) {
+ fun logStateUpdated(isRecording: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isRecording },
+ { "Updating state. isRecording=$bool1" },
+ )
+
+ fun logIntentStateUpdated(isRecording: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isRecording },
+ { "Update intent has state. isRecording=$bool1" },
+ )
+
+ fun logIntentMissingState() =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Received update intent with no state" })
+
+ fun logSentStartIntent() = logger.log(TAG, LogLevel.DEBUG, {}, { "Sent start intent" })
+
+ fun logPendingIntentCancelled(e: Exception) =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Pending intent was cancelled" }, e)
+
+ fun logCountdownCancelled() =
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Record countdown cancelled" })
+
+ fun logCountdownCancelErrorNoTimer() =
+ logger.log(TAG, LogLevel.ERROR, {}, { "Couldn't cancel countdown because timer was null" })
+
+ fun logRecordingStopped() = logger.log(TAG, LogLevel.DEBUG, {}, { "Stopping recording" })
+
+ fun logRecordingStopErrorNoStopIntent() =
+ logger.log(
+ TAG,
+ LogLevel.ERROR,
+ {},
+ { "Couldn't stop recording because stop intent was null" },
+ )
+
+ fun logRecordingStopError(e: Exception) =
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Couldn't stop recording" }, e)
+
+ companion object {
+ private const val TAG = "RecordingController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
index d7ddc50..a830e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
@@ -16,6 +16,9 @@
package com.android.systemui.screenrecord
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -53,7 +56,7 @@
@IntoMap
@StringKey(SCREEN_RECORD_TILE_SPEC)
fun provideScreenRecordAvailabilityInteractor(
- impl: ScreenRecordTileDataInteractor
+ impl: ScreenRecordTileDataInteractor
): QSTileAvailabilityInteractor
companion object {
@@ -89,5 +92,12 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @SysUISingleton
+ @RecordingControllerLog
+ fun provideRecordingControllerLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("RecordingControllerLog", 50)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
index 1868b4a..54e0319 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -40,7 +40,7 @@
private val intentExecutor: ActionIntentExecutor,
@Application private val applicationScope: CoroutineScope,
@Assisted val window: Window,
- @Assisted val viewProxy: ScreenshotViewProxy,
+ @Assisted val viewProxy: ScreenshotShelfViewProxy,
@Assisted val finishDismiss: () -> Unit,
) {
@@ -109,7 +109,7 @@
interface Factory {
fun create(
window: Window,
- viewProxy: ScreenshotViewProxy,
+ viewProxy: ScreenshotShelfViewProxy,
finishDismiss: (() -> Unit)
): ActionExecutor
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
deleted file mode 100644
index 3d024a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ /dev/null
@@ -1,242 +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.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.util.Log
-import android.view.KeyEvent
-import android.view.LayoutInflater
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.WindowInsets
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.res.R
-import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-/**
- * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
- * ScreenshotView.
- */
-class LegacyScreenshotViewProxy
-@AssistedInject
-constructor(
- private val logger: UiEventLogger,
- flags: FeatureFlags,
- @Assisted private val context: Context,
- @Assisted private val displayId: Int
-) : ScreenshotViewProxy {
- override val view: ScreenshotView =
- LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
- override val screenshotPreview: View
- override var packageName: String = ""
- set(value) {
- field = value
- view.setPackageName(value)
- }
- override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
- set(value) {
- field = value
- view.setCallbacks(value)
- }
- override var screenshot: ScreenshotData? = null
- set(value) {
- field = value
- value?.let {
- val badgeBg =
- AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
- val user = it.userHandle
- if (badgeBg != null && user != null) {
- view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
- }
- view.setScreenshot(it)
- }
- }
-
- override val isAttachedToWindow
- get() = view.isAttachedToWindow
- override val isDismissing
- get() = view.isDismissing
- override val isPendingSharedTransition
- get() = view.isPendingSharedTransition
-
- init {
- view.setUiEventLogger(logger)
- view.setDefaultDisplay(displayId)
- view.setFlags(flags)
- addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
- setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
- if (LogConfig.DEBUG_WINDOW) {
- Log.d(TAG, "adding OnComputeInternalInsetsListener")
- }
- view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
- screenshotPreview = view.screenshotPreview
- }
-
- override fun reset() = view.reset()
- override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
- override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
-
- override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
- view.createScreenshotDropInAnimation(screenRect, showFlash)
-
- override fun addQuickShareChip(quickShareAction: Notification.Action) =
- view.addQuickShareChip(quickShareAction)
-
- override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
- view.setChipIntents(imageData)
-
- override fun requestDismissal(event: ScreenshotEvent?) {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "screenshot dismissal requested")
- }
- // If we're already animating out, don't restart the animation
- if (view.isDismissing) {
- if (DEBUG_DISMISS) {
- Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
- }
- return
- }
- event?.let { logger.log(event, 0, packageName) }
- view.animateDismissal()
- }
-
- override fun showScrollChip(packageName: String, onClick: Runnable) =
- view.showScrollChip(packageName, onClick)
-
- override fun hideScrollChip() = view.hideScrollChip()
-
- override fun prepareScrollingTransition(
- response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
- newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean,
- onTransitionPrepared: Runnable,
- ) {
- view.prepareScrollingTransition(
- response,
- screenBitmap,
- newScreenshot,
- screenshotTakenInPortrait
- )
- view.post { onTransitionPrepared.run() }
- }
-
- override fun startLongScreenshotTransition(
- transitionDestination: Rect,
- onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
- ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
-
- override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
-
- override fun fadeForSharedTransition() {} // unused
-
- override fun stopInputListening() = view.stopInputListening()
-
- override fun requestFocus() {
- view.requestFocus()
- }
-
- override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
-
- override fun prepareEntranceAnimation(runnable: Runnable) {
- view.viewTreeObserver.addOnPreDrawListener(
- object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- if (LogConfig.DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation")
- }
- view.viewTreeObserver.removeOnPreDrawListener(this)
- runnable.run()
- return true
- }
- }
- )
- }
-
- private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
- val onBackInvokedCallback = OnBackInvokedCallback {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Predictive Back callback dispatched")
- }
- onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
- }
- view.addOnAttachStateChangeListener(
- object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Registering Predictive Back callback")
- }
- view
- .findOnBackInvokedDispatcher()
- ?.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackInvokedCallback
- )
- }
-
- override fun onViewDetachedFromWindow(view: View) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Unregistering Predictive Back callback")
- }
- view
- .findOnBackInvokedDispatcher()
- ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
- }
- }
- )
- }
- private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
- view.setOnKeyListener(
- object : View.OnKeyListener {
- override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
- if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "onKeyEvent: $keyCode")
- }
- onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
- return true
- }
- return false
- }
- }
- )
- }
-
- @AssistedFactory
- interface Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
- }
-
- companion object {
- private const val TAG = "LegacyScreenshotViewProxy"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5960462..474afa8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -137,7 +137,7 @@
val offset = container.height + params.topMargin + params.bottomMargin
val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
with(anim) {
- duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+ duration = MESSAGE_EXPANSION_DURATION_MS
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { valueAnimator: ValueAnimator ->
val interpolation = valueAnimator.animatedValue as Float
@@ -147,4 +147,8 @@
}
return anim
}
+
+ companion object {
+ const val MESSAGE_EXPANSION_DURATION_MS: Long = 400
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 95ee2e0..7739009 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
+import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
@@ -190,7 +191,7 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
- private final ScreenshotViewProxy mViewProxy;
+ private final ScreenshotShelfViewProxy mViewProxy;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -230,13 +231,6 @@
private String mPackageName = "";
private final BroadcastReceiver mCopyBroadcastReceiver;
- // When false, the screenshot is taken without showing the ui. Note that this only applies to
- // external displays, as on the default one the UI should **always** be shown.
- // This is needed in case of screenshot during display mirroring, as adding another window to
- // the external display makes mirroring stop.
- // When there is a way to distinguish between displays that are mirroring or extending, this
- // can be removed and we can directly show the ui only in the extended case.
- private final Boolean mShowUIOnExternalDisplay;
/** Tracks config changes that require re-creating UI */
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_ORIENTATION
@@ -252,7 +246,7 @@
Context context,
WindowManager windowManager,
FeatureFlags flags,
- ScreenshotViewProxy.Factory viewProxyFactory,
+ ScreenshotShelfViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
UiEventLogger uiEventLogger,
@@ -272,8 +266,7 @@
MessageContainerController messageContainerController,
Provider<ScreenshotSoundController> screenshotSoundController,
AnnouncementResolver announcementResolver,
- @Assisted Display display,
- @Assisted boolean showUIOnExternalDisplay
+ @Assisted Display display
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsControllerFactory.create(
@@ -347,7 +340,6 @@
mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
- mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
@Override
@@ -381,7 +373,7 @@
Log.w(TAG, "User setup not complete, displaying toast only");
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
// and sharing shouldn't be exposed to the user.
- saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
+ saveScreenshotAndToast(screenshot, finisher);
return;
}
@@ -397,17 +389,15 @@
prepareViewForNewScreenshot(screenshot, oldPackageName);
- if (!shouldShowUi()) {
- saveScreenshotInWorkerThread(
- screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
- (ignored) -> {
- });
- return;
- }
-
final UUID requestId;
requestId = mActionsController.setCurrentScreenshot(screenshot);
- saveScreenshotInBackground(screenshot, requestId, finisher);
+ saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
+ if (result.uri != null) {
+ ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp);
+ mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
+ }
+ });
if (screenshot.getTaskId() >= 0) {
mAssistContentRequester.requestAssistContent(
@@ -453,10 +443,6 @@
(v, insets) -> WindowInsets.CONSUMED);
}
- private boolean shouldShowUi() {
- return mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
- }
-
void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
@@ -491,9 +477,6 @@
}
mViewProxy.setPackageName(mPackageName);
-
- mViewProxy.updateOrientation(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
/**
@@ -542,7 +525,7 @@
}
mMessageContainerController.setView(mViewProxy.getView());
- mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
+ mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
if (DEBUG_INPUT) {
@@ -552,13 +535,6 @@
}
@Override
- public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
- mActionIntentExecutor.launchIntentAsync(
- intent, owner, overrideTransition, exit.first, exit.second);
- }
-
- @Override
public void onDismiss() {
finishDismiss();
}
@@ -724,29 +700,40 @@
* Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
* failure).
*/
- private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
+ private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) {
// Play the shutter sound to notify that we've taken a screenshot
playCameraSoundIfNeeded();
- saveScreenshotInWorkerThread(
- owner,
- /* onComplete */ finisher,
- /* actionsReadyListener */ imageData -> {
- if (DEBUG_CALLBACK) {
- Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
- }
- finisher.accept(imageData.uri);
- if (imageData.uri == null) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_save_text);
- } else {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
- mScreenshotHandler.post(() -> Toast.makeText(mContext,
- R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
- }
- },
- null);
+ if (screenshotSaveImageExporter()) {
+ saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+ if (result.uri != null) {
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ });
+ } else {
+ saveScreenshotInWorkerThread(
+ screenshot.getUserHandle(),
+ /* onComplete */ finisher,
+ /* actionsReadyListener */ imageData -> {
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG,
+ "returning URI to finisher (Consumer<URI>): " + imageData.uri);
+ }
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
+ mPackageName);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ },
+ null);
+ }
}
/**
@@ -819,8 +806,8 @@
mScreenshotHandler.cancelTimeout();
}
- private void saveScreenshotInBackground(
- ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
+ private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId,
+ Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) {
ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
mDisplay.getDisplayId());
@@ -829,10 +816,7 @@
ImageExporter.Result result = future.get();
Log.d(TAG, "Saved screenshot: " + result);
logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
- if (result.uri != null) {
- mActionsController.setCompletedScreenshot(requestId, new ScreenshotSavedResult(
- result.uri, screenshot.getUserOrDefault(), result.timestamp));
- }
+ onResult.accept(result);
if (DEBUG_CALLBACK) {
Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ "finisher.accept(\"" + result.uri + "\"");
@@ -877,62 +861,6 @@
mSaveInBgTask.execute();
}
-
- /**
- * Sets up the action shade and its entrance animation, once we get the screenshot URI.
- */
- private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
- logSuccessOnActionsReady(imageData);
- mScreenshotHandler.resetTimeout();
-
- if (imageData.uri != null) {
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI actions");
- }
- if (!imageData.owner.equals(Process.myUserHandle())) {
- Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
- + imageData.uri);
- }
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mViewProxy.setChipIntents(imageData);
- }
- });
- } else {
- mViewProxy.setChipIntents(imageData);
- }
- });
- }
- }
-
- /**
- * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
- */
- private void showUiOnQuickShareActionReady(ScreenshotController.QuickShareData quickShareData) {
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI for Quick Share action");
- }
- if (quickShareData.quickShareAction != null) {
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
- }
- });
- } else {
- mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
- }
- });
- }
- }
-
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
@@ -1028,9 +956,7 @@
* Creates an instance of the controller for that specific display.
*
* @param display display to capture
- * @param showUIOnExternalDisplay Whether the UI should be shown if this is an external
- * display.
*/
- ScreenshotController create(Display display, boolean showUIOnExternalDisplay);
+ ScreenshotController create(Display display);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 1b5fa34..50215af 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -18,7 +18,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.app.Notification
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
@@ -45,7 +44,6 @@
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
@@ -70,13 +68,23 @@
private val thumbnailObserver: ThumbnailObserver,
@Assisted private val context: Context,
@Assisted private val displayId: Int
-) : ScreenshotViewProxy {
- override val view: ScreenshotShelfView =
+) {
+
+ interface ScreenshotViewCallback {
+ fun onUserInteraction()
+
+ fun onDismiss()
+
+ /** DOWN motion event was observed outside of the touchable areas of this view. */
+ fun onTouchOutside()
+ }
+
+ val view: ScreenshotShelfView =
LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
- override val screenshotPreview: View
- override var packageName: String = ""
- override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
- override var screenshot: ScreenshotData? = null
+ val screenshotPreview: View
+ var packageName: String = ""
+ var callbacks: ScreenshotViewCallback? = null
+ var screenshot: ScreenshotData? = null
set(value) {
value?.let {
viewModel.setScreenshotBitmap(it.bitmap)
@@ -92,10 +100,11 @@
field = value
}
- override val isAttachedToWindow
+ val isAttachedToWindow
get() = view.isAttachedToWindow
- override var isDismissing = false
- override var isPendingSharedTransition = false
+
+ var isDismissing = false
+ var isPendingSharedTransition = false
private val animationController = ScreenshotAnimationController(view, viewModel)
private var inputMonitor: InputMonitorCompat? = null
@@ -136,17 +145,17 @@
)
}
- override fun reset() {
+ fun reset() {
animationController.cancel()
isPendingSharedTransition = false
viewModel.reset()
}
- override fun updateInsets(insets: WindowInsets) {
+
+ fun updateInsets(insets: WindowInsets) {
view.updateInsets(insets)
}
- override fun updateOrientation(insets: WindowInsets) {}
- override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
+ fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
val entrance =
animationController.getEntranceAnimation(screenRect, showFlash) {
viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
@@ -164,11 +173,7 @@
return entrance
}
- override fun addQuickShareChip(quickShareAction: Notification.Action) {}
-
- override fun setChipIntents(imageData: SavedImageData) {}
-
- override fun requestDismissal(event: ScreenshotEvent?) {
+ fun requestDismissal(event: ScreenshotEvent?) {
requestDismissal(event, null)
}
@@ -187,6 +192,7 @@
override fun onAnimationStart(animator: Animator) {
isDismissing = true
}
+
override fun onAnimationEnd(animator: Animator) {
isDismissing = false
callbacks?.onDismiss()
@@ -196,11 +202,7 @@
animator.start()
}
- override fun showScrollChip(packageName: String, onClick: Runnable) {}
-
- override fun hideScrollChip() {}
-
- override fun prepareScrollingTransition(
+ fun prepareScrollingTransition(
response: ScrollCaptureResponse,
screenBitmap: Bitmap, // unused
newScreenshot: Bitmap,
@@ -228,7 +230,7 @@
return r
}
- override fun startLongScreenshotTransition(
+ fun startLongScreenshotTransition(
transitionDestination: Rect,
onTransitionEnd: Runnable,
longScreenshot: ScrollCaptureController.LongScreenshot,
@@ -243,27 +245,27 @@
transitionAnimation.start()
}
- override fun restoreNonScrollingUi() {
+ fun restoreNonScrollingUi() {
viewModel.setScrollableRect(null)
viewModel.setScrollingScrimBitmap(null)
animationController.restoreUI()
callbacks?.onUserInteraction() // reset the timeout
}
- override fun stopInputListening() {
+ fun stopInputListening() {
inputMonitor?.dispose()
inputMonitor = null
inputEventReceiver?.dispose()
inputEventReceiver = null
}
- override fun requestFocus() {
+ fun requestFocus() {
view.requestFocus()
}
- override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+ fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
- override fun prepareEntranceAnimation(runnable: Runnable) {
+ fun prepareEntranceAnimation(runnable: Runnable) {
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
@@ -276,7 +278,7 @@
)
}
- override fun fadeForSharedTransition() {
+ fun fadeForSharedTransition() {
animationController.fadeForSharedTransition()
}
@@ -349,7 +351,7 @@
}
@AssistedFactory
- interface Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
+ interface Factory {
+ fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
deleted file mode 100644
index 59e38a8..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ /dev/null
@@ -1,1126 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
-import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.BroadcastOptions;
-import android.app.Notification;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.GestureDetector;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.res.R;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.util.ArrayList;
-
-/**
- * Handles the visual elements and animations for the screenshot flow.
- */
-public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
-
- public interface ScreenshotViewCallback {
- void onUserInteraction();
-
- void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
-
- void onDismiss();
-
- /** DOWN motion event was observed outside of the touchable areas of this view. */
- void onTouchOutside();
- }
-
- private static final String TAG = logTag(ScreenshotView.class);
-
- private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
- private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
- // delay before starting to fade in dismiss button
- private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
- private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
- private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
- private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
- private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
-
- private final Resources mResources;
- private final Interpolator mFastOutSlowIn;
- private final DisplayMetrics mDisplayMetrics;
- private final float mFixedSize;
- private final AccessibilityManager mAccessibilityManager;
- private final GestureDetector mSwipeDetector;
-
- private int mDefaultDisplay = Display.DEFAULT_DISPLAY;
- private int mNavMode;
- private boolean mOrientationPortrait;
- private boolean mDirectionLTR;
-
- private ImageView mScrollingScrim;
- private DraggableConstraintLayout mScreenshotStatic;
- private ImageView mScreenshotPreview;
- private ImageView mScreenshotBadge;
- private View mScreenshotPreviewBorder;
- private ImageView mScrollablePreview;
- private ImageView mScreenshotFlash;
- private ImageView mActionsContainerBackground;
- private HorizontalScrollView mActionsContainer;
- private LinearLayout mActionsView;
- private FrameLayout mDismissButton;
- private OverlayActionChip mShareChip;
- private OverlayActionChip mEditChip;
- private OverlayActionChip mScrollChip;
- private OverlayActionChip mQuickShareChip;
-
- private UiEventLogger mUiEventLogger;
- private ScreenshotViewCallback mCallbacks;
- private boolean mPendingSharedTransition;
- private InputMonitorCompat mInputMonitor;
- private InputChannelCompat.InputEventReceiver mInputEventReceiver;
- private boolean mShowScrollablePreview;
- private String mPackageName = "";
-
- private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
- private PendingInteraction mPendingInteraction;
- // Should only be set/used if the SCREENSHOT_METADATA flag is set.
- private ScreenshotData mScreenshotData;
-
- private final InteractionJankMonitor mInteractionJankMonitor;
- private FeatureFlags mFlags;
- private final Bundle mInteractiveBroadcastOption;
-
- private enum PendingInteraction {
- PREVIEW,
- EDIT,
- SHARE,
- QUICK_SHARE
- }
-
- public ScreenshotView(Context context) {
- this(context, null);
- }
-
- public ScreenshotView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public ScreenshotView(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mResources = mContext.getResources();
- mInteractionJankMonitor = getInteractionJankMonitorInstance();
-
- BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractive(true);
- mInteractiveBroadcastOption = options.toBundle();
-
- mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
-
- // standard material ease
- mFastOutSlowIn =
- AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
-
- mDisplayMetrics = new DisplayMetrics();
- mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
- mSwipeDetector = new GestureDetector(mContext,
- new GestureDetector.SimpleOnGestureListener() {
- final Rect mActionsRect = new Rect();
-
- @Override
- public boolean onScroll(
- MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mActionsContainer.getBoundsOnScreen(mActionsRect);
- // return true if we aren't in the actions bar, or if we are but it isn't
- // scrollable in the direction of movement
- return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
- || !mActionsContainer.canScrollHorizontally((int) distanceX);
- }
- });
- mSwipeDetector.setIsLongpressEnabled(false);
- addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- startInputListening();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- stopInputListening();
- }
- });
- }
-
- private InteractionJankMonitor getInteractionJankMonitorInstance() {
- return InteractionJankMonitor.getInstance();
- }
-
- public void hideScrollChip() {
- mScrollChip.setVisibility(View.GONE);
- }
-
- /**
- * Called to display the scroll action chip when support is detected.
- *
- * @param packageName the owning package of the window to be captured
- * @param onClick the action to take when the chip is clicked.
- */
- public void showScrollChip(String packageName, Runnable onClick) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "Showing Scroll option");
- }
- mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> onClick.run());
- }
-
- @Override // ViewTreeObserver.OnComputeInternalInsetsListener
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(getTouchRegion(true));
- }
-
- private Region getSwipeRegion() {
- Region swipeRegion = new Region();
-
- final Rect tmpRect = new Rect();
- int swipePadding = (int) FloatingWindowUtil.dpToPx(
- mDisplayMetrics, DraggableConstraintLayout.SWIPE_PADDING_DP * -1);
- mScreenshotPreview.getBoundsOnScreen(tmpRect);
- tmpRect.inset(swipePadding, swipePadding);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- mActionsContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset(swipePadding, swipePadding);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- mDismissButton.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
-
- View messageContainer = findViewById(R.id.screenshot_message_container);
- if (messageContainer != null) {
- messageContainer.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- }
- View messageDismiss = findViewById(R.id.message_dismiss_button);
- if (messageDismiss != null) {
- messageDismiss.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- }
-
- return swipeRegion;
- }
-
- private Region getTouchRegion(boolean includeScrim) {
- Region touchRegion = getSwipeRegion();
-
- if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
- final Rect tmpRect = new Rect();
- mScrollingScrim.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
- }
-
- if (QuickStepContract.isGesturalMode(mNavMode)) {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
- final Insets gestureInsets = windowMetrics.getWindowInsets().getInsets(
- WindowInsets.Type.systemGestures());
- // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
- Rect inset = new Rect(0, 0, gestureInsets.left, mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- inset.set(mDisplayMetrics.widthPixels - gestureInsets.right, 0,
- mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- }
- return touchRegion;
- }
-
- private void startInputListening() {
- stopInputListening();
- mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay);
- mInputEventReceiver = mInputMonitor.getInputReceiver(
- Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
- if (ev instanceof MotionEvent) {
- MotionEvent event = (MotionEvent) ev;
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && !getTouchRegion(false).contains(
- (int) event.getRawX(), (int) event.getRawY())) {
- mCallbacks.onTouchOutside();
- }
- }
- });
- }
-
- void stopInputListening() {
- if (mInputMonitor != null) {
- mInputMonitor.dispose();
- mInputMonitor = null;
- }
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- }
- }
-
- @Override // View
- protected void onFinishInflate() {
- super.onFinishInflate();
- mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
- mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
- mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
-
- mScreenshotPreviewBorder = requireNonNull(
- findViewById(R.id.screenshot_preview_border));
- mScreenshotPreview.setClipToOutline(true);
- mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
-
- mActionsContainerBackground = requireNonNull(findViewById(
- R.id.actions_container_background));
- mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
- mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
- mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
- mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
- mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
- mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
- mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
- mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
-
- setFocusable(true);
- mActionsContainer.setScrollX(0);
-
- mNavMode = getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode);
- mOrientationPortrait =
- getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
- mDirectionLTR =
- getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-
- // Get focus so that the key events go to the layout.
- setFocusableInTouchMode(true);
- requestFocus();
-
- mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
- @Override
- public void onInteraction() {
- mCallbacks.onUserInteraction();
- }
-
- @Override
- public void onSwipeDismissInitiated(Animator animator) {
- if (DEBUG_DISMISS) {
- Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
- mPackageName);
- }
-
- @Override
- public void onDismissComplete() {
- if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) {
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- }
- mCallbacks.onDismiss();
- }
- });
- }
-
- View getScreenshotPreview() {
- return mScreenshotPreview;
- }
-
- void setUiEventLogger(UiEventLogger uiEventLogger) {
- mUiEventLogger = uiEventLogger;
- }
-
- void setCallbacks(ScreenshotViewCallback callbacks) {
- mCallbacks = callbacks;
- }
-
- void setFlags(FeatureFlags flags) {
- mFlags = flags;
- }
-
- void setScreenshot(Bitmap bitmap, Insets screenInsets) {
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
- }
-
- void setScreenshot(ScreenshotData screenshot) {
- mScreenshotData = screenshot;
- setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
- screenshot.getInsets()));
- }
-
- void setPackageName(String packageName) {
- mPackageName = packageName;
- }
-
- void setDefaultDisplay(int displayId) {
- mDefaultDisplay = displayId;
- }
-
- void updateInsets(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- FrameLayout.LayoutParams p =
- (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
- DisplayCutout cutout = insets.getDisplayCutout();
- Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
- if (cutout == null) {
- p.setMargins(0, 0, 0, navBarInsets.bottom);
- } else {
- Insets waterfall = cutout.getWaterfallInsets();
- if (mOrientationPortrait) {
- p.setMargins(
- waterfall.left,
- Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom)));
- } else {
- p.setMargins(
- Math.max(cutout.getSafeInsetLeft(), waterfall.left),
- waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right),
- Math.max(navBarInsets.bottom, waterfall.bottom));
- }
- }
- mScreenshotStatic.setLayoutParams(p);
- mScreenshotStatic.requestLayout();
- }
-
- void updateOrientation(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- updateInsets(insets);
- ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
- if (mOrientationPortrait) {
- params.width = (int) mFixedSize;
- params.height = LayoutParams.WRAP_CONTENT;
- mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
- } else {
- params.width = LayoutParams.WRAP_CONTENT;
- params.height = (int) mFixedSize;
- mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
- }
-
- mScreenshotPreview.setLayoutParams(params);
- }
-
- AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
- }
-
- Rect targetPosition = new Rect();
- mScreenshotPreview.getHitRect(targetPosition);
-
- // ratio of preview width, end vs. start size
- float cornerScale =
- mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
- final float currentScale = 1 / cornerScale;
-
- AnimatorSet dropInAnimation = new AnimatorSet();
- ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
- flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
- flashInAnimator.setInterpolator(mFastOutSlowIn);
- flashInAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
- flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
- flashOutAnimator.setInterpolator(mFastOutSlowIn);
- flashOutAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- // animate from the current location, to the static preview location
- final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
- final PointF finalPos = new PointF(targetPosition.exactCenterX(),
- targetPosition.exactCenterY());
-
- // Shift to screen coordinates so that the animation runs on top of the entire screen,
- // including e.g. bars covering the display cutout.
- int[] locInScreen = mScreenshotPreview.getLocationOnScreen();
- startPos.offset(targetPosition.left - locInScreen[0], targetPosition.top - locInScreen[1]);
-
- if (DEBUG_ANIM) {
- Log.d(TAG, "toCorner: startPos=" + startPos);
- Log.d(TAG, "toCorner: finalPos=" + finalPos);
- }
-
- ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
- toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
-
- toCorner.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mScreenshotPreview.setScaleX(currentScale);
- mScreenshotPreview.setScaleY(currentScale);
- mScreenshotPreview.setVisibility(View.VISIBLE);
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setAlpha(0);
- mDismissButton.setVisibility(View.VISIBLE);
- }
- }
- });
-
- float xPositionPct =
- SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float dismissPct =
- SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float scalePct =
- SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- toCorner.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- if (t < scalePct) {
- float scale = MathUtils.lerp(
- currentScale, 1, mFastOutSlowIn.getInterpolation(t / scalePct));
- mScreenshotPreview.setScaleX(scale);
- mScreenshotPreview.setScaleY(scale);
- } else {
- mScreenshotPreview.setScaleX(1);
- mScreenshotPreview.setScaleY(1);
- }
-
- if (t < xPositionPct) {
- float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
- mFastOutSlowIn.getInterpolation(t / xPositionPct));
- mScreenshotPreview.setX(xCenter - mScreenshotPreview.getWidth() / 2f);
- } else {
- mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
- }
- float yCenter = MathUtils.lerp(
- startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
- mScreenshotPreview.setY(yCenter - mScreenshotPreview.getHeight() / 2f);
-
- if (t >= dismissPct) {
- mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
- float currentX = mScreenshotPreview.getX();
- float currentY = mScreenshotPreview.getY();
- mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
- if (mDirectionLTR) {
- mDismissButton.setX(currentX + mScreenshotPreview.getWidth()
- - mDismissButton.getWidth() / 2f);
- } else {
- mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
- }
- }
- });
-
- mScreenshotFlash.setAlpha(0f);
- mScreenshotFlash.setVisibility(View.VISIBLE);
-
- ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
- borderFadeIn.setDuration(100);
- borderFadeIn.addUpdateListener((animation) -> {
- float borderAlpha = animation.getAnimatedFraction();
- mScreenshotPreviewBorder.setAlpha(borderAlpha);
- mScreenshotBadge.setAlpha(borderAlpha);
- });
-
- if (showFlash) {
- dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
- dropInAnimation.play(flashOutAnimator).with(toCorner);
- } else {
- dropInAnimation.play(toCorner);
- }
- dropInAnimation.play(borderFadeIn).after(toCorner);
-
- dropInAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- CUJ_TAKE_SCREENSHOT, mScreenshotPreview)
- .setTag("DropIn");
- mInteractionJankMonitor.begin(builder);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "drop-in animation ended");
- }
- mDismissButton.setOnClickListener(view -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "dismiss button clicked");
- }
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, 0, mPackageName);
- animateDismissal();
- });
- mDismissButton.setAlpha(1);
- float dismissOffset = mDismissButton.getWidth() / 2f;
- float finalDismissX = mDirectionLTR
- ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
- : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
- mDismissButton.setX(finalDismissX);
- mDismissButton.setY(
- finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
- mScreenshotPreview.setScaleX(1);
- mScreenshotPreview.setScaleY(1);
- mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
- mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f);
- requestLayout();
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- createScreenshotActionsShadeAnimation().start();
- }
- });
-
- return dropInAnimation;
- }
-
- ValueAnimator createScreenshotActionsShadeAnimation() {
- // By default the activities won't be able to start immediately; override this to keep
- // the same behavior as if started from a notification
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- ArrayList<OverlayActionChip> chips = new ArrayList<>();
-
- mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
- mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
- mShareChip.setOnClickListener(v -> {
- mShareChip.setIsPending(true);
- mEditChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.SHARE;
- });
- chips.add(mShareChip);
-
- mEditChip.setContentDescription(
- mContext.getString(R.string.screenshot_edit_description));
- mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit),
- true);
- mEditChip.setOnClickListener(v -> {
- mEditChip.setIsPending(true);
- mShareChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.EDIT;
- });
- chips.add(mEditChip);
-
- mScreenshotPreview.setOnClickListener(v -> {
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.PREVIEW;
- });
-
- mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
- mScrollChip.setIcon(Icon.createWithResource(mContext,
- R.drawable.ic_screenshot_scroll), true);
- chips.add(mScrollChip);
-
- // remove the margin from the last chip so that it's correctly aligned with the end
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
- mActionsView.getChildAt(0).getLayoutParams();
- params.setMarginEnd(0);
- mActionsView.getChildAt(0).setLayoutParams(params);
-
- ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
- float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
- / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
- mActionsContainer.setAlpha(0f);
- mActionsContainerBackground.setAlpha(0f);
- mActionsContainer.setVisibility(View.VISIBLE);
- mActionsContainerBackground.setVisibility(View.VISIBLE);
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
- .setTag("Actions")
- .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
- mInteractionJankMonitor.begin(builder);
- }
- });
-
- animator.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
- mActionsContainer.setAlpha(containerAlpha);
- mActionsContainerBackground.setAlpha(containerAlpha);
- float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
- + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
- mActionsContainer.setScaleX(containerScale);
- mActionsContainerBackground.setScaleX(containerScale);
- for (OverlayActionChip chip : chips) {
- chip.setAlpha(t);
- chip.setScaleX(1 / containerScale); // invert to keep size of children constant
- }
- mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainerBackground.setPivotX(
- mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
- });
- return animator;
- }
-
- void badgeScreenshot(@Nullable Drawable badge) {
- mScreenshotBadge.setImageDrawable(badge);
- mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
- }
-
- void setChipIntents(ScreenshotController.SavedImageData imageData) {
- mShareChip.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
- prepareSharedTransition();
-
- Intent shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
- imageData.uri, imageData.subject);
- mCallbacks.onAction(shareIntent, imageData.owner, false);
-
- });
- mEditChip.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
- prepareSharedTransition();
- mCallbacks.onAction(
- ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.owner, true);
- });
- mScreenshotPreview.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
- prepareSharedTransition();
- mCallbacks.onAction(
- ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.owner, true);
- });
- if (mQuickShareChip != null) {
- if (imageData.quickShareAction != null) {
- mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
- () -> {
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
- mPackageName);
- animateDismissal();
- });
- } else {
- // hide chip and unset pending interaction if necessary, since we don't actually
- // have a useable quick share intent
- Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
- if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
- mPendingInteraction = null;
- }
- mQuickShareChip.setVisibility(GONE);
- }
- }
-
- if (mPendingInteraction != null) {
- switch (mPendingInteraction) {
- case PREVIEW:
- mScreenshotPreview.callOnClick();
- break;
- case SHARE:
- mShareChip.callOnClick();
- break;
- case EDIT:
- mEditChip.callOnClick();
- break;
- case QUICK_SHARE:
- mQuickShareChip.callOnClick();
- break;
- }
- } else {
- LayoutInflater inflater = LayoutInflater.from(mContext);
-
- for (Notification.Action smartAction : imageData.smartActions) {
- OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
- R.layout.overlay_action_chip, mActionsView, false);
- actionChip.setText(smartAction.title);
- actionChip.setIcon(smartAction.getIcon(), false);
- actionChip.setPendingIntent(smartAction.actionIntent,
- () -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED,
- 0, mPackageName);
- animateDismissal();
- });
- actionChip.setAlpha(1);
- mActionsView.addView(actionChip, mActionsView.getChildCount() - 1);
- mSmartChips.add(actionChip);
- }
- }
- }
-
- void addQuickShareChip(Notification.Action quickShareAction) {
- if (mQuickShareChip != null) {
- mSmartChips.remove(mQuickShareChip);
- mActionsView.removeView(mQuickShareChip);
- }
- if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
- mPendingInteraction = null;
- }
- if (mPendingInteraction == null) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mQuickShareChip = (OverlayActionChip) inflater.inflate(
- R.layout.overlay_action_chip, mActionsView, false);
- mQuickShareChip.setText(quickShareAction.title);
- mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
- mQuickShareChip.setOnClickListener(v -> {
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- mQuickShareChip.setIsPending(true);
- mPendingInteraction = PendingInteraction.QUICK_SHARE;
- });
- mQuickShareChip.setAlpha(1);
- mActionsView.addView(mQuickShareChip);
- mSmartChips.add(mQuickShareChip);
- }
- }
-
- private Rect scrollableAreaOnScreen(ScrollCaptureResponse response) {
- Rect r = new Rect(response.getBoundsInWindow());
- Rect windowInScreen = response.getWindowBounds();
- r.offset(windowInScreen.left, windowInScreen.top);
- r.intersect(new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
- return r;
- }
-
- void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
- ScrollCaptureController.LongScreenshot longScreenshot) {
- mPendingSharedTransition = true;
- AnimatorSet animSet = new AnimatorSet();
-
- ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
- scrimAnim.addUpdateListener(animation ->
- mScrollingScrim.setAlpha(1 - animation.getAnimatedFraction()));
-
- if (mShowScrollablePreview) {
- mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
- float startX = mScrollablePreview.getX();
- float startY = mScrollablePreview.getY();
- int[] locInScreen = mScrollablePreview.getLocationOnScreen();
- destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
- mScrollablePreview.setPivotX(0);
- mScrollablePreview.setPivotY(0);
- mScrollablePreview.setAlpha(1f);
- float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
- Matrix matrix = new Matrix();
- matrix.setScale(currentScale, currentScale);
- matrix.postTranslate(
- longScreenshot.getLeft() * currentScale,
- longScreenshot.getTop() * currentScale);
- mScrollablePreview.setImageMatrix(matrix);
- float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
-
- ValueAnimator previewAnim = ValueAnimator.ofFloat(0, 1);
- previewAnim.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- float currScale = MathUtils.lerp(1, destinationScale, t);
- mScrollablePreview.setScaleX(currScale);
- mScrollablePreview.setScaleY(currScale);
- mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
- mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
- });
- ValueAnimator previewFadeAnim = ValueAnimator.ofFloat(1, 0);
- previewFadeAnim.addUpdateListener(animation ->
- mScrollablePreview.setAlpha(1 - animation.getAnimatedFraction()));
- animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim);
- previewAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- onTransitionEnd.run();
- }
- });
- } else {
- // if we switched orientations between the original screenshot and the long screenshot
- // capture, just fade out the scrim instead of running the preview animation
- animSet.play(scrimAnim);
- animSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- onTransitionEnd.run();
- }
- });
- }
- animSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mCallbacks.onDismiss();
- }
- });
- animSet.start();
- }
-
- void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
- Bitmap newBitmap, boolean screenshotTakenInPortrait) {
- mShowScrollablePreview = (screenshotTakenInPortrait == mOrientationPortrait);
-
- mScrollingScrim.setImageBitmap(newBitmap);
- mScrollingScrim.setVisibility(View.VISIBLE);
-
- if (mShowScrollablePreview) {
- Rect scrollableArea = scrollableAreaOnScreen(response);
-
- float scale = mFixedSize
- / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
- ConstraintLayout.LayoutParams params =
- (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
-
- params.width = (int) (scale * scrollableArea.width());
- params.height = (int) (scale * scrollableArea.height());
- Matrix matrix = new Matrix();
- matrix.setScale(scale, scale);
- matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
-
- mScrollablePreview.setTranslationX(scale
- * (mDirectionLTR ? scrollableArea.left : scrollableArea.right - getWidth()));
- mScrollablePreview.setTranslationY(scale * scrollableArea.top);
- mScrollablePreview.setImageMatrix(matrix);
- mScrollablePreview.setImageBitmap(screenBitmap);
- mScrollablePreview.setVisibility(View.VISIBLE);
- }
- mDismissButton.setVisibility(View.GONE);
- mActionsContainer.setVisibility(View.GONE);
- // set these invisible, but not gone, so that the views are laid out correctly
- mActionsContainerBackground.setVisibility(View.INVISIBLE);
- mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
- mScreenshotPreview.setVisibility(View.INVISIBLE);
- mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
- ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
- anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
- ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
- anim.setDuration(200);
- anim.start();
- }
-
- void restoreNonScrollingUi() {
- mScrollChip.setVisibility(View.GONE);
- mScrollablePreview.setVisibility(View.GONE);
- mScrollingScrim.setVisibility(View.GONE);
-
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setVisibility(View.VISIBLE);
- }
- mActionsContainer.setVisibility(View.VISIBLE);
- mActionsContainerBackground.setVisibility(View.VISIBLE);
- mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
- mScreenshotPreview.setVisibility(View.VISIBLE);
- // reset the timeout
- mCallbacks.onUserInteraction();
- }
-
- boolean isDismissing() {
- return mScreenshotStatic.isDismissing();
- }
-
- boolean isPendingSharedTransition() {
- return mPendingSharedTransition;
- }
-
- void animateDismissal() {
- mScreenshotStatic.dismiss();
- }
-
- void reset() {
- if (DEBUG_UI) {
- Log.d(TAG, "reset screenshot view");
- }
- mScreenshotStatic.cancelDismissal();
- if (DEBUG_WINDOW) {
- Log.d(TAG, "removing OnComputeInternalInsetsListener");
- }
- // Make sure we clean up the view tree observer
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- // Clear any references to the bitmap
- mScreenshotPreview.setImageDrawable(null);
- mScreenshotPreview.setVisibility(View.INVISIBLE);
- mScreenshotPreview.setAlpha(1f);
- mScreenshotPreviewBorder.setAlpha(0);
- mScreenshotBadge.setAlpha(0f);
- mScreenshotBadge.setVisibility(View.GONE);
- mScreenshotBadge.setImageDrawable(null);
- mPendingSharedTransition = false;
- mActionsContainerBackground.setVisibility(View.INVISIBLE);
- mActionsContainer.setVisibility(View.GONE);
- mDismissButton.setVisibility(View.GONE);
- mScrollingScrim.setVisibility(View.GONE);
- mScrollablePreview.setVisibility(View.GONE);
- mScreenshotStatic.setTranslationX(0);
- mScreenshotPreview.setContentDescription(
- mContext.getResources().getString(R.string.screenshot_preview_description));
- mScreenshotPreview.setOnClickListener(null);
- mShareChip.setOnClickListener(null);
- mScrollingScrim.setVisibility(View.GONE);
- mEditChip.setOnClickListener(null);
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- mPendingInteraction = null;
- for (OverlayActionChip chip : mSmartChips) {
- mActionsView.removeView(chip);
- }
- mSmartChips.clear();
- mQuickShareChip = null;
- setAlpha(1);
- mScreenshotStatic.setAlpha(1);
- mScreenshotData = null;
- }
-
- private void prepareSharedTransition() {
- mPendingSharedTransition = true;
- // fade out non-preview UI
- createScreenshotFadeDismissAnimation().start();
- }
-
- ValueAnimator createScreenshotFadeDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = 1 - animation.getAnimatedFraction();
- mDismissButton.setAlpha(alpha);
- mActionsContainerBackground.setAlpha(alpha);
- mActionsContainer.setAlpha(alpha);
- mScreenshotPreviewBorder.setAlpha(alpha);
- mScreenshotBadge.setAlpha(alpha);
- });
- alphaAnim.setDuration(600);
- return alphaAnim;
- }
-
- /**
- * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
- */
- private static Drawable createScreenDrawable(Resources res, Bitmap bitmap, Insets insets) {
- int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
- int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
-
- BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
- if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
- || bitmap.getHeight() == 0) {
- Log.e(TAG, "Can't create inset drawable, using 0 insets bitmap and insets create "
- + "degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " "
- + bitmapDrawable);
- return bitmapDrawable;
- }
-
- InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
- -1f * insets.left / insettedWidth,
- -1f * insets.top / insettedHeight,
- -1f * insets.right / insettedWidth,
- -1f * insets.bottom / insettedHeight);
-
- if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
- // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
- // to fill in the background of the drawable.
- return new LayerDrawable(new Drawable[]{
- new ColorDrawable(Color.BLACK), insetDrawable});
- } else {
- return insetDrawable;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
deleted file mode 100644
index df93a5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ /dev/null
@@ -1,76 +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.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-
-/** Abstraction of the surface between ScreenshotController and ScreenshotView */
-interface ScreenshotViewProxy {
- val view: ViewGroup
- val screenshotPreview: View
-
- var packageName: String
- var callbacks: ScreenshotView.ScreenshotViewCallback?
- var screenshot: ScreenshotData?
-
- val isAttachedToWindow: Boolean
- val isDismissing: Boolean
- val isPendingSharedTransition: Boolean
-
- fun reset()
- fun updateInsets(insets: WindowInsets)
- fun updateOrientation(insets: WindowInsets)
- fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
- fun addQuickShareChip(quickShareAction: Notification.Action)
- fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun requestDismissal(event: ScreenshotEvent?)
-
- fun showScrollChip(packageName: String, onClick: Runnable)
- fun hideScrollChip()
- fun prepareScrollingTransition(
- response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
- newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean,
- onTransitionPrepared: Runnable,
- )
- fun startLongScreenshotTransition(
- transitionDestination: Rect,
- onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
- )
- fun restoreNonScrollingUi()
- fun fadeForSharedTransition()
-
- fun stopInputListening()
- fun requestFocus()
- fun announceForAccessibility(string: String)
- fun prepareEntranceAnimation(runnable: Runnable)
-
- interface Factory {
- fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 2699657..07f6e85 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -219,7 +219,7 @@
}
private fun getScreenshotController(display: Display): ScreenshotController {
- val controller = screenshotController ?: screenshotControllerFactory.create(display, false)
+ val controller = screenshotController ?: screenshotControllerFactory.create(display)
screenshotController = controller
return controller
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 8c833ec..bd9e295 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -33,6 +33,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
import android.graphics.RecordingCanvas;
@@ -267,6 +268,8 @@
}
private boolean canAppStartThroughLauncher(String packageName) {
+ // Use Intent.resolveActivity API to check if the intent resolves as that is what Android
+ // uses internally when apps use Context.startActivity.
return getMainLauncherIntentForPackage(packageName).resolveActivity(mPackageManager)
!= null;
}
@@ -366,10 +369,19 @@
return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
}
- private Intent getMainLauncherIntentForPackage(String packageName) {
- return new Intent(ACTION_MAIN)
- .addCategory(CATEGORY_LAUNCHER)
- .setPackage(packageName);
+ private Intent getMainLauncherIntentForPackage(String pkgName) {
+ Intent intent = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER).setPackage(pkgName);
+
+ // Not all apps use DEFAULT_CATEGORY for their main launcher activity so the exact component
+ // needs to be queried and set on the Intent in order for note-taking apps to be able to
+ // start this intent. When starting an activity with an implicit intent, Android adds the
+ // DEFAULT_CATEGORY flag otherwise it fails to resolve the intent.
+ ResolveInfo resolvedActivity = mPackageManager.resolveActivity(intent, /* flags= */ 0);
+ if (resolvedActivity != null) {
+ intent.setComponent(resolvedActivity.getComponentInfo().getComponentName());
+ }
+
+ return intent;
}
/** Helper factory to help with injecting {@link AppClipsViewModel}. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 56ba1af4..682f848 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -24,12 +24,10 @@
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
-import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
-import com.android.systemui.screenshot.ScreenshotViewProxy;
import com.android.systemui.screenshot.TakeScreenshotExecutor;
import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
@@ -92,8 +90,4 @@
AccessibilityManager accessibilityManager) {
return new ScreenshotViewModel(accessibilityManager);
}
-
- @Binds
- abstract ScreenshotViewProxy.Factory bindScreenshotViewProxyFactory(
- ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 49810762..8e53949 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,24 @@
public void startObserving() {
if (!mObserving) {
mObserving = true;
- mSecureSettings.registerContentObserverForUserSync(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
}
public void stopObserving() {
- mSecureSettings.unregisterContentObserverSync(this);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(this);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(this);
+ }
mObserving = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 04de2c2..c1caeed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1774,8 +1774,9 @@
// the small clock here
// With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
if (!MigrateClocksToBlueprint.isEnabled()) {
+ boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
- && hasVisibleNotifications() && isOnAod()) {
+ && hasVisibleNotifications() && (isOnAod() || bypassEnabled)) {
return SMALL;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 4d43ad5..a4fed873 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -19,8 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -105,18 +103,26 @@
@Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
val legacyExpandImmediate: StateFlow<Boolean>
- val shadeMode: StateFlow<ShadeMode>
-
/** Whether dual shade should be aligned to the bottom (true) or to the top (false). */
val isDualShadeAlignedToBottom: Boolean
+ /**
+ * Whether the shade layout should be wide (true) or narrow (false).
+ *
+ * In a wide layout, notifications and quick settings each take up only half the screen width
+ * (whether they are shown at the same time or not). In a narrow layout, they can each be as
+ * wide as the entire screen.
+ */
+ val isShadeLayoutWide: StateFlow<Boolean>
+
/** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
@Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
/** NPVC.mClosing as a flow. */
@Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
- fun setShadeMode(mode: ShadeMode)
+ /** Sets whether the shade layout should be wide (true) or narrow (false). */
+ fun setShadeLayoutWide(isShadeLayoutWide: Boolean)
/** Sets whether a closing animation is happening. */
@Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
@@ -183,6 +189,7 @@
class ShadeRepositoryImpl @Inject constructor(@Application applicationContext: Context) :
ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
+ @Deprecated("Use ShadeInteractor.qsExpansion instead")
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
private val _lockscreenShadeExpansion = MutableStateFlow(0f)
@@ -204,6 +211,7 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow()
+ @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
private val _legacyQsTracking = MutableStateFlow(false)
@@ -227,20 +235,22 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
- val _shadeMode = MutableStateFlow(if (DualShade.isEnabled) ShadeMode.Dual else ShadeMode.Single)
- override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+ private val _isShadeLayoutWide = MutableStateFlow(false)
+ override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
override val isDualShadeAlignedToBottom =
applicationContext.resources.getBoolean(R.bool.config_dualShadeAlignedToBottom)
- override fun setShadeMode(shadeMode: ShadeMode) {
- _shadeMode.value = shadeMode
+ override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
+ _isShadeLayoutWide.value = isShadeLayoutWide
}
+ @Deprecated("Use ShadeInteractor instead")
override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
_legacyQsFullscreen.value = legacyQsFullscreen
}
+ @Deprecated("Use ShadeInteractor instead")
override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
_legacyExpandImmediate.value = legacyExpandImmediate
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index ef0a842..45f359e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -52,9 +52,25 @@
/** Are touches allowed on the notification panel? */
val isShadeTouchable: Flow<Boolean>
- /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
+ /** Whether the shade can be expanded from QQS to QS. */
val isExpandToQsEnabled: Flow<Boolean>
+ /**
+ * The version of the shade layout to use.
+ *
+ * Note: Most likely, you want to read [isShadeLayoutWide] instead of this.
+ */
+ val shadeMode: StateFlow<ShadeMode>
+
+ /**
+ * Whether the shade layout should be wide (true) or narrow (false).
+ *
+ * In a wide layout, notifications and quick settings each take up only half the screen width
+ * (whether they are shown at the same time or not). In a narrow layout, they can each be as
+ * wide as the entire screen.
+ */
+ val isShadeLayoutWide: StateFlow<Boolean>
+
/** How to align the shade content. */
val shadeAlignment: ShadeAlignment
}
@@ -113,8 +129,6 @@
* animating.
*/
val isUserInteractingWithQs: Flow<Boolean>
-
- val shadeMode: StateFlow<ShadeMode>
}
fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6226d07..e77aca9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -47,5 +47,6 @@
override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
+ override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
override val shadeAlignment: ShadeAlignment = ShadeAlignment.Top
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 55f019b..684a484 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,7 +25,9 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeAlignment
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -60,8 +62,7 @@
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
disableFlagsRepository.disableFlags
- .map { isDisabledByFlags -> isDisabledByFlags.isShadeEnabled() }
- .distinctUntilChanged()
+ .map { it.isShadeEnabled() }
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
override val isQsEnabled: StateFlow<Boolean> =
@@ -102,6 +103,17 @@
}
}
+ override val isShadeLayoutWide: StateFlow<Boolean> = shadeRepository.isShadeLayoutWide
+
+ override val shadeMode: StateFlow<ShadeMode> =
+ isShadeLayoutWide
+ .map(this::determineShadeMode)
+ .stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ initialValue = determineShadeMode(isShadeLayoutWide.value)
+ )
+
override val shadeAlignment: ShadeAlignment =
if (shadeRepository.isDualShadeAlignedToBottom) {
ShadeAlignment.Bottom
@@ -125,4 +137,12 @@
disableFlags.isQuickSettingsEnabled() &&
!isDozing
}
+
+ private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
+ return when {
+ DualShade.isEnabled -> ShadeMode.Dual
+ isShadeLayoutWide -> ShadeMode.Split
+ else -> ShadeMode.Single
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 7d46d2b..f39ee9a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -105,8 +104,6 @@
override val isUserInteractingWithQs: Flow<Boolean> =
userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
- override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
-
/**
* Return a flow for whether a user is interacting with an expandable shade component using
* tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index d5b4f4d..b2142a5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -22,8 +22,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -45,10 +43,7 @@
@Application scope: CoroutineScope,
sceneInteractor: SceneInteractor,
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
- shadeRepository: ShadeRepository,
) : BaseShadeInteractor {
- override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
-
override val shadeExpansion: StateFlow<Float> =
sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
.stateIn(scope, SharingStarted.Eagerly, 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 354d379..5eb3a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -29,8 +29,6 @@
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.PulseExpansionHandler
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -40,7 +38,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -63,7 +60,7 @@
) : CoreStartable {
override fun start() {
- hydrateShadeMode()
+ hydrateShadeLayoutWidth()
hydrateShadeExpansionStateManager()
logTouchesTo(touchLog)
scrimShadeTransitionController.init()
@@ -86,22 +83,17 @@
}
}
- private fun hydrateShadeMode() {
- if (DualShade.isEnabled) {
- shadeRepository.setShadeMode(ShadeMode.Dual)
- return
- }
+ private fun hydrateShadeLayoutWidth() {
applicationScope.launch {
configurationRepository.onAnyConfigurationChange
// Force initial collection.
.onStart { emit(Unit) }
- .map { applicationContext.resources }
- .map { resources ->
- splitShadeStateController.shouldUseSplitNotificationShade(resources)
- }
- .collect { isSplitShade ->
- shadeRepository.setShadeMode(
- if (isSplitShade) ShadeMode.Split else ShadeMode.Single
+ .collect {
+ val resources = applicationContext.resources
+ // The configuration for 'shouldUseSplitNotificationShade' dictates the width of
+ // the shade in both split-shade and dual-shade modes.
+ shadeRepository.setShadeLayoutWide(
+ splitShadeStateController.shouldUseSplitNotificationShade(resources)
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index 8214a24..a8199a4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -29,6 +29,8 @@
/**
* The split shade where, on large screens and unfolded foldables, the QS and notification parts
* are placed side-by-side and expand/collapse as a single panel.
+ *
+ * Note: This isn't the only mode where the shade is wide.
*/
data object Split : ShadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 2f58b35..ea4e065 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -18,51 +18,35 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.data.repository.SmartspaceRepositoryModule
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
import javax.inject.Named
-@Module(subcomponents = [SmartspaceViewComponent::class],
- includes = [SmartspaceRepositoryModule::class])
+@Module(subcomponents = [SmartspaceViewComponent::class])
abstract class SmartspaceModule {
@Module
companion object {
- /**
- * The BcSmartspaceDataProvider for dreams.
- */
+ /** The BcSmartspaceDataProvider for dreams. */
const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
- /**
- * The BcSmartspaceDataPlugin for the standalone weather on dream.
- */
+ /** The BcSmartspaceDataPlugin for the standalone weather on dream. */
const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin"
- /**
- * The target filter for smartspace over lockscreen.
- */
+ /** The target filter for smartspace over lockscreen. */
const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
- /**
- * The precondition for smartspace over lockscreen
- */
+ /** The precondition for smartspace over lockscreen */
const val LOCKSCREEN_SMARTSPACE_PRECONDITION = "lockscreen_smartspace_precondition"
- /**
- * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
- */
+ /** The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd). */
const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
- /**
- * The BcSmartspaceDataPlugin for the standalone weather.
- */
+ /** The BcSmartspaceDataPlugin for the standalone weather. */
const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
- /**
- * The BcSmartspaceDataProvider for the glanceable hub.
- */
+ /** The BcSmartspaceDataProvider for the glanceable hub. */
const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin"
}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
deleted file mode 100644
index 52a1c15..0000000
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.smartspace.data.repository
-
-import android.app.smartspace.SmartspaceTarget
-import android.os.Parcelable
-import android.widget.RemoteViews
-import com.android.systemui.communal.smartspace.CommunalSmartspaceController
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
-
-interface SmartspaceRepository {
- /** Whether [RemoteViews] are passed through smartspace targets. */
- val isSmartspaceRemoteViewsEnabled: Boolean
-
- /** Smartspace targets for the communal surface. */
- val communalSmartspaceTargets: Flow<List<SmartspaceTarget>>
-}
-
-@SysUISingleton
-class SmartspaceRepositoryImpl
-@Inject
-constructor(
- private val communalSmartspaceController: CommunalSmartspaceController,
- @Main private val uiExecutor: Executor,
-) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
-
- override val isSmartspaceRemoteViewsEnabled: Boolean
- get() = android.app.smartspace.flags.Flags.remoteViews()
-
- private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
- MutableStateFlow(emptyList())
- override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
- _communalSmartspaceTargets
- .onStart {
- uiExecutor.execute {
- communalSmartspaceController.addListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
- }
- .onCompletion {
- uiExecutor.execute {
- communalSmartspaceController.removeListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
- }
-
- override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
- targetsNullable?.let { targets ->
- _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
- }
- ?: run { _communalSmartspaceTargets.value = emptyList() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
deleted file mode 100644
index c77bcc5..0000000
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.smartspace.data.repository
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface SmartspaceRepositoryModule {
- @Binds fun smartspaceRepository(impl: SmartspaceRepositoryImpl): SmartspaceRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 342ec0d..c997ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -19,6 +19,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -149,7 +150,7 @@
private KeyCharacterMap mBackupKeyCharacterMap;
@VisibleForTesting
- KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
+ KeyboardShortcutListSearch(Context context, WindowManager windowManager, int deviceId) {
this.mContext = new ContextThemeWrapper(
context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
@@ -159,12 +160,12 @@
this.mWindowManager = mContext.getSystemService(WindowManager.class);
}
loadResources(this.mContext);
- createHardcodedShortcuts();
+ createHardcodedShortcuts(deviceId);
}
- private static KeyboardShortcutListSearch getInstance(Context context) {
+ private static KeyboardShortcutListSearch getInstance(Context context, int deviceId) {
if (sInstance == null) {
- sInstance = new KeyboardShortcutListSearch(context, null);
+ sInstance = new KeyboardShortcutListSearch(context, null, deviceId);
}
return sInstance;
}
@@ -176,7 +177,7 @@
if (sInstance != null && !sInstance.mContext.equals(context)) {
dismiss();
}
- getInstance(context).showKeyboardShortcuts(deviceId);
+ getInstance(context, deviceId).showKeyboardShortcuts(deviceId);
}
}
@@ -274,7 +275,8 @@
context.getString(R.string.keyboard_key_button_template, "Mode"));
mSpecialCharacterNames.put(
KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
- mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_ESCAPE, context.getString(R.string.keyboard_key_esc));
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
@@ -366,7 +368,7 @@
KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
}
- private void createHardcodedShortcuts() {
+ private void createHardcodedShortcuts(int deviceId) {
// Add system shortcuts
mKeySearchResultMap.put(SHORTCUT_SYSTEM_INDEX, true);
mSystemGroup.add(getMultiMappingSystemShortcuts(mContext));
@@ -376,7 +378,7 @@
mInputGroup.add(getMultiMappingInputShortcuts(mContext));
// Add open apps shortcuts
final List<KeyboardShortcutMultiMappingGroup> appShortcuts =
- Arrays.asList(getDefaultMultiMappingApplicationShortcuts());
+ Arrays.asList(getDefaultMultiMappingApplicationShortcuts(deviceId));
if (appShortcuts != null && !appShortcuts.isEmpty()) {
mOpenAppsGroup = appShortcuts;
mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, true);
@@ -738,35 +740,50 @@
shortcutMultiMappingInfoList);
}
- private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts() {
- final int userId = mContext.getUserId();
- PackageInfo assistPackageInfo = getAssistPackageInfo(mContext, mPackageManager, userId);
- CharSequence categoryTitle =
- mContext.getString(R.string.keyboard_shortcut_group_applications);
+ private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts(
+ int deviceId) {
List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ List<KeyboardShortcutMultiMappingGroup> shortcuts =
+ reMapToKeyboardShortcutMultiMappingGroup(Arrays.asList(apps));
+ for (KeyboardShortcutMultiMappingGroup group : shortcuts) {
+ for (ShortcutMultiMappingInfo keyboardShortcutInfo : group.getItems()) {
+ shortcutMultiMappingInfos.add(keyboardShortcutInfo);
+ }
+ }
+ categoryTitle = apps.getLabel();
+ } else {
+ // Show shortcuts based on AOSP bookmarks.xml
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ final int userId = mContext.getUserId();
+ PackageInfo assistPackageInfo =
+ getAssistPackageInfo(mContext, mPackageManager, userId);
- String[] intentCategories = {
- Intent.CATEGORY_APP_BROWSER,
- Intent.CATEGORY_APP_CONTACTS,
- Intent.CATEGORY_APP_EMAIL,
- Intent.CATEGORY_APP_CALENDAR,
- Intent.CATEGORY_APP_MAPS,
- Intent.CATEGORY_APP_MUSIC,
- Intent.CATEGORY_APP_MESSAGING,
- Intent.CATEGORY_APP_CALCULATOR,
+ String[] intentCategories = {
+ Intent.CATEGORY_APP_BROWSER,
+ Intent.CATEGORY_APP_CONTACTS,
+ Intent.CATEGORY_APP_EMAIL,
+ Intent.CATEGORY_APP_CALENDAR,
+ Intent.CATEGORY_APP_MAPS,
+ Intent.CATEGORY_APP_MUSIC,
+ Intent.CATEGORY_APP_MESSAGING,
+ Intent.CATEGORY_APP_CALCULATOR,
+ };
+ String[] shortcutLabels = {
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
+ };
- };
- String[] shortcutLabels = {
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
- };
- int[] keyCodes = {
+ int[] keyCodes = {
KeyEvent.KEYCODE_B,
KeyEvent.KEYCODE_C,
KeyEvent.KEYCODE_E,
@@ -775,52 +792,44 @@
KeyEvent.KEYCODE_P,
KeyEvent.KEYCODE_S,
KeyEvent.KEYCODE_U,
- };
+ };
- // Assist.
- if (assistPackageInfo != null) {
+ // Assist.
if (assistPackageInfo != null) {
- final Icon assistIcon = Icon.createWithResource(
- assistPackageInfo.applicationInfo.packageName,
- assistPackageInfo.applicationInfo.icon);
- CharSequence assistLabel =
- mContext.getString(R.string.keyboard_shortcut_group_applications_assist);
- KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
- assistLabel,
- assistIcon,
- KeyEvent.KEYCODE_A,
- KeyEvent.META_META_ON);
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(
- assistLabel,
- assistIcon,
- Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
+ CharSequence assistLabel = mContext.getString(
+ R.string.keyboard_shortcut_group_applications_assist);
+ KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
+ assistLabel,
+ assistIcon,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON);
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(
+ assistLabel,
+ assistIcon,
+ Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ }
}
- }
- // Browser (Chrome as default): Meta + B
- // Contacts: Meta + C
- // Email (Gmail as default): Meta + E
- // Gmail: Meta + G
- // Calendar: Meta + K
- // Maps: Meta + M
- // Music: Meta + P
- // SMS: Meta + S
- // Calculator: Meta + U
- for (int i = 0; i < shortcutLabels.length; i++) {
- final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
- if (icon != null) {
- CharSequence label =
- shortcutLabels[i];
- KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
- label,
- icon,
- keyCodes[i],
- KeyEvent.META_META_ON);
- List<ShortcutKeyGroup> shortcutKeyGroups =
- Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ for (int i = 0; i < shortcutLabels.length; i++) {
+ final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
+ if (icon != null) {
+ CharSequence label =
+ shortcutLabels[i];
+ KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
+ label,
+ icon,
+ keyCodes[i],
+ KeyEvent.META_META_ON);
+ List<ShortcutKeyGroup> shortcutKeyGroups =
+ Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ }
}
}
@@ -1220,7 +1229,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 6ee13c3..766c391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -20,6 +20,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -75,6 +76,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
/**
* Contains functionality for handling keyboard shortcuts.
@@ -133,6 +135,7 @@
@Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
@Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+ @Nullable private KeyboardShortcutGroup mDefaultApplicationShortcuts = null;
@VisibleForTesting
KeyboardShortcuts(Context context, WindowManager windowManager) {
@@ -259,7 +262,8 @@
context.getString(R.string.keyboard_key_button_template, "Mode"));
mSpecialCharacterNames.put(
KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
- mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_ESCAPE, context.getString(R.string.keyboard_key_esc));
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
@@ -389,6 +393,7 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
+ mDefaultApplicationShortcuts = getDefaultApplicationShortcuts(deviceId);
mWindowManager.requestAppKeyboardShortcuts(
result -> {
mBackgroundHandler.post(() -> {
@@ -442,10 +447,8 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
- final KeyboardShortcutGroup defaultAppShortcuts =
- getDefaultApplicationShortcuts();
- if (defaultAppShortcuts != null) {
- shortcutGroups.add(defaultAppShortcuts);
+ if (mDefaultApplicationShortcuts != null) {
+ shortcutGroups.add(mDefaultApplicationShortcuts);
}
shortcutGroups.add(getSystemShortcuts());
showKeyboardShortcutsDialog(shortcutGroups);
@@ -498,7 +501,7 @@
return systemGroup;
}
- private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
+ private KeyboardShortcutGroup getDefaultApplicationShortcuts(int deviceId) {
final int userId = mContext.getUserId();
List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
@@ -523,70 +526,82 @@
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.KEYCODE_A,
KeyEvent.META_META_ON));
}
}
- // Browser.
- final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
- if (browserIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- browserIcon,
- KeyEvent.KEYCODE_B,
- KeyEvent.META_META_ON));
- }
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ categoryTitle = apps.getLabel();
+ keyboardShortcutInfoAppItems.addAll(apps.getItems());
+ } else {
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ // Browser.
+ final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
+ if (browserIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ browserIcon,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.META_META_ON));
+ }
- // Contacts.
- final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
- if (contactsIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- contactsIcon,
- KeyEvent.KEYCODE_C,
- KeyEvent.META_META_ON));
- }
+ // Contacts.
+ final Icon contactsIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CONTACTS, userId);
+ if (contactsIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ contactsIcon,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.META_META_ON));
+ }
- // Email.
- final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
- if (emailIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- emailIcon,
- KeyEvent.KEYCODE_E,
- KeyEvent.META_META_ON));
- }
+ // Email.
+ final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
+ if (emailIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ emailIcon,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.META_META_ON));
+ }
- // Messaging.
- final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
- if (messagingIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- messagingIcon,
- KeyEvent.KEYCODE_S,
- KeyEvent.META_META_ON));
- }
+ // Messaging.
+ final Icon messagingIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_MESSAGING, userId);
+ if (messagingIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ messagingIcon,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON));
+ }
- // Music.
- final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
- if (musicIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- musicIcon,
- KeyEvent.KEYCODE_P,
- KeyEvent.META_META_ON));
- }
+ // Music.
+ final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
+ if (musicIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ musicIcon,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.META_META_ON));
+ }
- // Calendar.
- final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
- if (calendarIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- calendarIcon,
- KeyEvent.KEYCODE_K,
- KeyEvent.META_META_ON));
+ // Calendar.
+ final Icon calendarIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CALENDAR, userId);
+ if (calendarIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ calendarIcon,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.META_META_ON));
+ }
}
final int itemsSize = keyboardShortcutInfoAppItems.size();
@@ -597,7 +612,7 @@
// Sorts by label, case insensitive with nulls and/or empty labels last.
Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
return new KeyboardShortcutGroup(
- mContext.getString(R.string.keyboard_shortcut_group_applications),
+ categoryTitle,
keyboardShortcutInfoAppItems,
true);
}
@@ -776,7 +791,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index e505ef7..0957e5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -51,6 +51,7 @@
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
@@ -223,6 +224,10 @@
mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(),
this::calculateStateFromSceneFramework),
this::onStatusBarStateChanged);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mKeyguardTransitionInteractorLazy.get().transitionValue(KeyguardState.AOD),
+ this::onAodKeyguardStateTransitionValueChanged);
}
}
@@ -693,6 +698,14 @@
updateStateAndNotifyListeners(newState);
}
+ private void onAodKeyguardStateTransitionValueChanged(float value) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ setDozeAmountInternal(value);
+ }
+
private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
Scenes.Lockscreen, StatusBarState.KEYGUARD,
Scenes.Bouncer, StatusBarState.KEYGUARD,
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 ed1756a..11ccdff 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
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -48,6 +51,7 @@
interactor: CallChipInteractor,
systemClock: SystemClock,
private val activityStarter: ActivityStarter,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.ongoingCallState
@@ -86,9 +90,9 @@
}
return View.OnClickListener { view ->
+ logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
- // TODO(b/332662551): Log the click event.
// This mimics OngoingCallController#updateChipClickListener.
activityStarter.postStartActivityDismissingKeyguard(
state.intent,
@@ -108,5 +112,6 @@
R.string.ongoing_phone_call_content_description,
),
)
+ private const val TAG = "CallVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 6917f46..7c95f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -18,7 +18,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.policy.CastDevice
import javax.inject.Inject
@@ -38,6 +41,7 @@
constructor(
@Application private val scope: CoroutineScope,
private val mediaRouterRepository: MediaRouterRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
private val activeCastDevice: StateFlow<CastDevice?> =
mediaRouterRepository.castDevices
@@ -49,8 +53,10 @@
activeCastDevice
.map {
if (it != null) {
+ logger.log(TAG, LogLevel.INFO, { str1 = it.name }, { "State: Casting($str1)" })
MediaRouterCastModel.Casting(deviceName = it.name)
} else {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
MediaRouterCastModel.DoingNothing
}
}
@@ -60,4 +66,8 @@
fun stopCasting() {
activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
}
+
+ companion object {
+ private const val TAG = "MediaRouter"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index cac3f25..bafec38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -51,33 +51,34 @@
}
private fun getMessage(): String {
+ val hostDeviceName = state.projectionState.hostDeviceName
return if (state.projectionState is MediaProjectionState.Projecting.SingleTask) {
val appBeingSharedName =
endMediaProjectionDialogHelper.getAppName(state.projectionState)
- if (appBeingSharedName != null && state.deviceName != null) {
+ if (appBeingSharedName != null && hostDeviceName != null) {
context.getString(
R.string.cast_to_other_device_stop_dialog_message_specific_app_with_device,
appBeingSharedName,
- state.deviceName,
+ hostDeviceName,
)
} else if (appBeingSharedName != null) {
context.getString(
R.string.cast_to_other_device_stop_dialog_message_specific_app,
appBeingSharedName,
)
- } else if (state.deviceName != null) {
+ } else if (hostDeviceName != null) {
context.getString(
R.string.cast_to_other_device_stop_dialog_message_generic_with_device,
- state.deviceName
+ hostDeviceName,
)
} else {
context.getString(R.string.cast_to_other_device_stop_dialog_message_generic)
}
} else {
- if (state.deviceName != null) {
+ if (hostDeviceName != null) {
context.getString(
R.string.cast_to_other_device_stop_dialog_message_entire_screen_with_device,
- state.deviceName
+ hostDeviceName,
)
} else {
context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 4183cdd..afa9cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -18,12 +18,14 @@
import android.content.Context
import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
@@ -58,8 +60,8 @@
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
/**
* The cast chip to show, based only on MediaProjection API events.
@@ -125,6 +127,16 @@
override val chip: StateFlow<OngoingActivityChipModel> =
combine(projectionChip, routerChip) { projection, router ->
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = projection.logName
+ str2 = router.logName
+ },
+ { "projectionChip=$str1 > routerChip=$str2" }
+ )
+
// A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
// different times when *screen* casting:
//
@@ -151,10 +163,13 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
mediaProjectionChipInteractor.stopProjecting()
}
+ /** Stops the currently active media route. */
private fun stopMediaRouterCasting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
mediaRouterChipInteractor.stopCasting()
}
@@ -175,7 +190,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createCastScreenToOtherDeviceDialogDelegate(state),
- dialogTransitionAnimator,
+ logger,
+ TAG,
),
)
}
@@ -191,7 +207,8 @@
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
createGenericCastToOtherDeviceDialogDelegate(deviceName),
- dialogTransitionAnimator,
+ logger,
+ TAG,
),
)
}
@@ -216,5 +233,6 @@
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+ private const val TAG = "CastToOtherVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index ce60fab..191c221 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -71,11 +71,11 @@
{
str1 = type.name
str2 = state.hostPackage
+ str3 = state.hostDeviceName
},
- { "State: Projecting(type=$str1 hostPackage=$str2)" }
+ { "State: Projecting(type=$str1 hostPackage=$str2 hostDevice=$str3)" }
)
- // TODO(b/351851835): Get the device name.
- ProjectionChipModel.Projecting(type, state, deviceName = null)
+ ProjectionChipModel.Projecting(type, state)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
index a1a5e82..85682f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
@@ -26,16 +26,10 @@
/** There is no media being projected. */
data object NotProjecting : ProjectionChipModel()
- /**
- * Media is currently being projected.
- *
- * @property deviceName the name of the device receiving the projection, or null if the
- * projection is to this device (as opposed to a different device).
- */
+ /** Media is currently being projected. */
data class Projecting(
val type: Type,
val projectionState: MediaProjectionState.Projecting,
- val deviceName: String?,
) : ProjectionChipModel()
enum class Type {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index df25d57..0c34981 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -19,13 +19,15 @@
import android.app.ActivityManager
import android.content.Context
import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -52,7 +54,7 @@
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.screenRecordState
@@ -78,7 +80,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createDelegate(state.recordedTask),
- dialogTransitionAnimator,
+ logger,
+ TAG,
),
)
}
@@ -93,12 +96,18 @@
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
context,
- stopAction = interactor::stopRecording,
+ stopAction = this::stopRecording,
recordedTask,
)
}
+ private fun stopRecording() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+ interactor.stopRecording()
+ }
+
companion object {
@DrawableRes val ICON = R.drawable.ic_screenrecord
+ private const val TAG = "ScreenRecordVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index c097720..ddebd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -18,12 +18,14 @@
import android.content.Context
import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -52,8 +54,8 @@
private val context: Context,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
mediaProjectionChipInteractor.projection
@@ -74,6 +76,7 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
mediaProjectionChipInteractor.stopProjecting()
}
@@ -89,10 +92,7 @@
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(
- createShareToAppDialogDelegate(state),
- dialogTransitionAnimator
- ),
+ createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
)
}
@@ -106,5 +106,6 @@
companion object {
@DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+ private const val TAG = "ShareToAppVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 0dbf5d6..ee010f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import android.view.View
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlinx.coroutines.flow.StateFlow
@@ -36,19 +36,13 @@
/** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
fun createDialogLaunchOnClickListener(
dialogDelegate: SystemUIDialog.Delegate,
- dialogTransitionAnimator: DialogTransitionAnimator,
+ @StatusBarChipsLog logger: LogBuffer,
+ tag: String,
): View.OnClickListener {
- return View.OnClickListener { view ->
+ return View.OnClickListener { _ ->
+ logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
- val launchableView =
- view.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- // TODO(b/343699052): This makes a beautiful animate-in, but the
- // animate-out looks odd because the dialog animates back into the chip
- // but then the chip disappears. If we aren't able to address
- // b/343699052 in time for launch, we should just use `dialog.show`.
- dialogTransitionAnimator.showFromView(dialog, launchableView)
+ dialog.show()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
index ef96f43..9ca110e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -93,8 +93,8 @@
private val occlusionStateFromFinishedStep =
combine(
keyguardTransitionInteractor.isFinishedIn(
- Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
keyguardTransitionInteractor.isFinishedIn(KeyguardState.OCCLUDED),
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
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 f98a88f..e48c28d 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
@@ -698,7 +698,7 @@
}
public void setHeadsUpIsVisible() {
- if (row != null) row.setHeadsUpIsVisible();
+ if (row != null) row.markHeadsUpSeen();
}
//TODO: i'm imagining a world where this isn't just the row, but I could be rwong
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 1434dc0..55c6790 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -125,7 +125,10 @@
// Whether or not keyguard is visible (or occluded).
val isKeyguardPresent: Flow<Boolean> =
keyguardTransitionInteractor
- .transitionValue(Scenes.Gone, stateWithoutSceneContainer = KeyguardState.GONE)
+ .transitionValue(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = KeyguardState.GONE,
+ )
.map { it == 0f }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index f166d32..5adf31b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
@@ -13,18 +13,12 @@
/** The subset of active listeners which are temporary (will be removed after called) */
private val temporaryListeners = ArraySet<OnReorderingAllowedListener>()
- private val banListeners = ListenerSet<OnReorderingBannedListener>()
-
var isReorderingAllowed = true
set(value) {
if (field != value) {
field = value
if (value) {
notifyReorderingAllowed()
- } else {
- banListeners.forEach { listener ->
- listener.onReorderingBanned()
- }
}
}
}
@@ -44,10 +38,6 @@
allListeners.addIfAbsent(listener)
}
- fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) {
- banListeners.addIfAbsent(listener)
- }
-
/** Add a listener which will be removed when it is called. */
fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) {
// Only add to the temporary set if it was added to the global set
@@ -67,8 +57,3 @@
fun interface OnReorderingAllowedListener {
fun onReorderingAllowed()
}
-
-fun interface OnReorderingBannedListener {
- fun onReorderingBanned()
-}
-
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 b63ee4c..ca5f49d 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
@@ -25,6 +25,7 @@
import com.android.settingslib.notification.data.repository.ZenModeRepository;
import com.android.settingslib.notification.data.repository.ZenModeRepositoryImpl;
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
@@ -288,6 +289,7 @@
@Background Handler handler
) {
return new ZenModeRepositoryImpl(context, notificationManager,
+ ZenModesBackend.getInstance(context), context.getContentResolver(),
coroutineScope, coroutineContext, handler);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index eebbb13..bf44b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.notification.domain.interactor
@@ -25,17 +25,14 @@
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
class HeadsUpNotificationInteractor
@Inject
@@ -50,48 +47,54 @@
val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
/** Set of currently pinned top-level heads up rows to be displayed. */
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
- if (repositories.isNotEmpty()) {
- val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
- repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } }
- combine(toCombine) { pairs ->
- pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
+ if (repositories.isNotEmpty()) {
+ val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
+ repositories.map { repo ->
+ repo.isPinned.map { isPinned -> repo to isPinned }
+ }
+ combine(toCombine) { pairs ->
+ pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ }
+ } else {
+ // if the set is empty, there are no flows to combine
+ flowOf(emptySet())
}
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(emptySet())
}
}
+ }
/** Are there any pinned heads up rows to display? */
- val hasPinnedRows: Flow<Boolean> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
- if (rows.isNotEmpty()) {
- combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(false)
- }
- }
-
- val isHeadsUpOrAnimatingAway: Flow<Boolean> =
- combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
- hasPinnedRows,
- animatingAway ->
- hasPinnedRows || animatingAway
- }
- .debounce { isHeadsUpOrAnimatingAway ->
- if (isHeadsUpOrAnimatingAway) {
- 0
+ val hasPinnedRows: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
+ if (rows.isNotEmpty()) {
+ combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
} else {
- // When the last pinned entry is removed from the [HeadsUpRepository],
- // there might be a delay before the View starts animating.
- 50L
+ // if the set is empty, there are no flows to combine
+ flowOf(false)
}
}
- .onStart { emit(false) } // emit false, so we don't wait for the initial update
- .distinctUntilChanged()
+ }
+ }
+
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
+ hasPinnedRows,
+ animatingAway ->
+ hasPinnedRows || animatingAway
+ }
+ }
+ }
private val canShowHeadsUp: Flow<Boolean> =
combine(
@@ -109,10 +112,15 @@
}
}
- val showHeadsUpStatusBar: Flow<Boolean> =
- combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
- hasPinnedRows && canShowHeadsUp
+ val showHeadsUpStatusBar: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+ hasPinnedRows && canShowHeadsUp
+ }
}
+ }
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
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 9e943ee..1cbb16e 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
@@ -371,12 +371,6 @@
setUserExpanded(nowExpanded);
}
- if (ExpandHeadsUpOnInlineReply.isEnabled() && mExpandable) {
- // it is triggered by the user.
- // So, mHasUserChangedExpansion should be marked true.
- mHasUserChangedExpansion = true;
- }
-
notifyHeightChanged(/* needsAnimation= */ true);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
if (shouldLogExpandClickMetric) {
@@ -865,8 +859,8 @@
}
@Override
- public void setHeadsUpIsVisible() {
- super.setHeadsUpIsVisible();
+ public void markHeadsUpSeen() {
+ super.markHeadsUpSeen();
mMustStayOnScreen = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 2af119f..6becbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -637,7 +637,10 @@
return false;
}
- public void setHeadsUpIsVisible() {
+ /**
+ * Called, when the notification has been seen by the user in the heads up state.
+ */
+ public void markHeadsUpSeen() {
}
public boolean showingPulsing() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index b5ea861..b8af369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Notification
+import android.app.Notification.RichOngoingStyle
import android.app.PendingIntent
import android.content.Context
import android.util.Log
@@ -68,12 +69,14 @@
builder: Notification.Builder,
systemUIContext: Context,
packageContext: Context
- ): RichOngoingContentModel? =
+ ): RichOngoingContentModel? {
+ if (builder.style !is RichOngoingStyle) return null
+
try {
val sbn = entry.sbn
val notification = sbn.notification
val icon = IconModel(notification.smallIcon)
- if (sbn.packageName == "com.google.android.deskclock") {
+ return if (sbn.packageName == "com.google.android.deskclock") {
when (notification.channelId) {
"Timers v2" -> {
parseTimerNotification(notification, icon)
@@ -90,8 +93,9 @@
} else null
} catch (e: Exception) {
Log.e("RONs", "Error parsing RON", e)
- null
+ return null
}
+ }
/**
* FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d1e5ab0..83de226 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -176,7 +176,7 @@
expandableView.setInShelf(inShelf);
if (headsUpIsVisible) {
- expandableView.setHeadsUpIsVisible();
+ expandableView.markHeadsUpSeen();
}
}
}
@@ -231,7 +231,7 @@
expandableView.setInShelf(this.inShelf);
if (headsUpIsVisible) {
- expandableView.setHeadsUpIsVisible();
+ expandableView.markHeadsUpSeen();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dab3799..2081adc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -715,6 +715,7 @@
}
public float getNotificationSquishinessFraction() {
+ SceneContainerFlag.assertInLegacyMode();
return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
}
@@ -860,7 +861,7 @@
/* label= */ "getHeight() - mKeyguardBottomPadding = " + y);
}
- y = getHeight() - getEmptyBottomMargin();
+ y = getHeight() - getEmptyBottomMarginInternal();
drawDebugInfo(canvas, y, Color.GREEN,
/* label= */ "getHeight() - getEmptyBottomMargin() = " + y);
@@ -1270,7 +1271,7 @@
private void updateAlgorithmLayoutMinHeight() {
mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition()
- ? getLayoutMinHeight() : 0);
+ ? getLayoutMinHeightInternal() : 0);
}
/**
@@ -1444,7 +1445,7 @@
} else {
if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
final float endHeight = updateStackEndHeight(
- getHeight(), getEmptyBottomMargin(), getTopPadding());
+ getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
updateStackHeight(endHeight, fraction);
} else {
// Always updateStackHeight to prevent jumps in the stack height when this fraction
@@ -1617,7 +1618,7 @@
float appear;
float expandAmount;
if (mKeyguardBypassEnabled && onKeyguard()) {
- appear = calculateAppearFractionBypass();
+ appear = calculateAppearFractionBypassInternal();
expandAmount = getPulseHeight();
} else {
appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight));
@@ -1642,6 +1643,7 @@
* Return the height of the content ignoring the footer.
*/
public int getIntrinsicContentHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return (int) mIntrinsicContentHeight;
}
@@ -2373,6 +2375,11 @@
* @return the first child which has visibility unequal to GONE
*/
public ExpandableView getFirstChildNotGone() {
+ SceneContainerFlag.assertInLegacyMode();
+ return getFirstChildNotGoneInternal();
+ }
+
+ private ExpandableView getFirstChildNotGoneInternal() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
@@ -2432,6 +2439,7 @@
* @return the number of children which have visibility unequal to GONE
*/
public int getNotGoneChildCount() {
+ SceneContainerFlag.assertInLegacyMode();
int childCount = getChildCount();
int count = 0;
for (int i = 0; i < childCount; i++) {
@@ -2642,7 +2650,7 @@
*/
public void updateTopPadding(float qsHeight, boolean animate) {
int topPadding = (int) qsHeight;
- int minStackHeight = getLayoutMinHeight();
+ int minStackHeight = getLayoutMinHeightInternal();
if (topPadding + minStackHeight > getHeight()) {
mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
} else {
@@ -2658,6 +2666,11 @@
}
public int getLayoutMinHeight() {
+ SceneContainerFlag.assertInLegacyMode();
+ return getLayoutMinHeightInternal();
+ }
+
+ private int getLayoutMinHeightInternal() {
if (isHeadsUpTransition()) {
ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow();
if (trackedHeadsUpRow.isAboveShelf()) {
@@ -3430,7 +3443,7 @@
* @return Whether a y coordinate is inside the content.
*/
public boolean isInContentBounds(float y) {
- return y < getHeight() - getEmptyBottomMargin();
+ return y < getHeight() - getEmptyBottomMarginInternal();
}
private float getTouchSlop(MotionEvent event) {
@@ -3506,7 +3519,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startOverscrollAfterExpanding() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
getExpandHelper().finishExpanding();
setIsBeingDragged(true);
}
@@ -3514,7 +3527,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startDraggingOnHun() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
setIsBeingDragged(true);
}
@@ -4063,10 +4076,16 @@
}
boolean isScrolledToBottom() {
+ SceneContainerFlag.assertInLegacyMode();
return mScrollAdapter.isScrolledToBottom();
}
int getEmptyBottomMargin() {
+ SceneContainerFlag.assertInLegacyMode();
+ return getEmptyBottomMarginInternal();
+ }
+
+ private int getEmptyBottomMarginInternal() {
int contentHeight;
if (mShouldUseSplitNotificationShade) {
// When in split shade and there are no notifications, the height can be too low, as
@@ -4249,7 +4268,7 @@
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
if (view instanceof ExpandableNotificationRow row && !onKeyguard()) {
// TODO: once we're recycling this will need to check the adapter position of the child
- if (row.isUserLocked() && row != getFirstChildNotGone()) {
+ if (row.isUserLocked() && row != getFirstChildNotGoneInternal()) {
if (row.isSummaryWithChildren()) {
return;
}
@@ -4536,7 +4555,7 @@
// clipped when pulsing
float ownTranslationZ = 0;
if (mKeyguardBypassEnabled && mAmbientState.isHiddenAtAll()) {
- ExpandableView firstChildNotGone = getFirstChildNotGone();
+ ExpandableView firstChildNotGone = getFirstChildNotGoneInternal();
if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
ownTranslationZ = firstChildNotGone.getTranslationZ();
}
@@ -4684,10 +4703,12 @@
}
public int getEmptyShadeViewHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mEmptyShadeView.getHeight();
}
public float getBottomMostNotificationBottom() {
+ SceneContainerFlag.assertInLegacyMode();
final int count = getChildCount();
float max = 0;
for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -5110,6 +5131,7 @@
}
public float getOpeningHeight() {
+ SceneContainerFlag.assertInLegacyMode();
if (mEmptyShadeView.getVisibility() == GONE) {
return getMinExpansionHeight();
} else {
@@ -5566,6 +5588,11 @@
}
public float calculateAppearFractionBypass() {
+ SceneContainerFlag.assertInLegacyMode();
+ return calculateAppearFractionBypassInternal();
+ }
+
+ private float calculateAppearFractionBypassInternal() {
float pulseHeight = getPulseHeight();
// The total distance required to fully reveal the header
float totalDistance = getIntrinsicPadding();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 36930bf..a072ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1004,6 +1004,7 @@
}
public int getRight() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getRight();
}
@@ -1015,6 +1016,7 @@
* @return the left of the view.
*/
public int getLeft() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getLeft();
}
@@ -1022,6 +1024,7 @@
* @return the top of the view.
*/
public int getTop() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getTop();
}
@@ -1029,6 +1032,7 @@
* @return the bottom of the view.
*/
public int getBottom() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getBottom();
}
@@ -1158,6 +1162,7 @@
}
public int getIntrinsicContentHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getIntrinsicContentHeight();
}
@@ -1216,6 +1221,7 @@
}
public float getX() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getX();
}
@@ -1224,14 +1230,17 @@
}
public float getWidth() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getWidth();
}
public float getOpeningHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getOpeningHeight();
}
public float getBottomMostNotificationBottom() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getBottomMostNotificationBottom();
}
@@ -1266,10 +1275,12 @@
}
public float getNotificationSquishinessFraction() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getNotificationSquishinessFraction();
}
public float calculateAppearFractionBypass() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.calculateAppearFractionBypass();
}
@@ -1279,22 +1290,27 @@
}
public boolean isScrolledToBottom() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.isScrolledToBottom();
}
public int getNotGoneChildCount() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getNotGoneChildCount();
}
public float getIntrinsicPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getIntrinsicPadding();
}
public float getLayoutMinHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getLayoutMinHeight();
}
public int getEmptyBottomMargin() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getEmptyBottomMargin();
}
@@ -1307,6 +1323,7 @@
}
public float getEmptyShadeViewHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getEmptyShadeViewHeight();
}
@@ -1474,6 +1491,7 @@
}
public boolean isShowingEmptyShadeView() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.isEmptyShadeViewVisible();
}
@@ -1602,6 +1620,7 @@
}
public ExpandableView getFirstChildNotGone() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getFirstChildNotGone();
}
@@ -1713,6 +1732,7 @@
}
public boolean isLongPressInProgress() {
+ SceneContainerFlag.assertInLegacyMode();
return mLongPressedView != null;
}
@@ -1722,6 +1742,7 @@
* from the keyguard host to the quick settings one.
*/
public int getFullShadeTransitionInset() {
+ SceneContainerFlag.assertInLegacyMode();
MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
if (view == null || view.getHeight() == 0
|| mStatusBarStateController.getState() != KEYGUARD) {
@@ -2013,6 +2034,7 @@
hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
if (hunWantsIt) {
mView.startDraggingOnHun();
+ mHeadsUpManager.unpinAll(true);
}
}
boolean swipeWantsIt = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 497ffca..6881d11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -38,12 +38,14 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class SharedNotificationContainerBinder
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index b54f9c4..5fba615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -275,13 +275,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- combine(
- notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.isShadeFullyCollapsed
- ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
- !isKeyguardShowing && isShadeFullyCollapsed
- }
- .dumpWhileCollecting("headsUpAnimationsEnabled")
+ flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index eb1f778..1a7bc16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -110,6 +110,11 @@
interactor.setScrolledToTop(scrolledToTop)
}
+ /** Sets whether the heads up notification is animating away. */
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
+
/** Snooze the currently pinned HUN. */
fun snoozeHun() {
headsUpNotificationInteractor.snooze()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 900201f..d1d5d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -141,6 +141,7 @@
private val communalSceneInteractor: CommunalSceneInteractor,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
+ // TODO(b/349784682): Transform deprecated states for Flexiglass
private val statesForConstrainedNotifications: Set<KeyguardState> =
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
private val statesForHiddenKeyguard: Set<KeyguardState> = setOf(GONE, OCCLUDED)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index 52cb48b..8d73983 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -51,8 +51,8 @@
}
removed.forEach { key ->
val row = obtainView(key)
- parentView.generateHeadsUpAnimation(row, /* isHeadsUp = */ false)
- row.setHeadsUpIsVisible()
+ parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ row.markHeadsUpSeen()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 91b5d0b..a538856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -414,6 +414,14 @@
}
}
+ @Override
+ public void onFeatureEnabledChanged(boolean enabled) {
+ if (!enabled) {
+ mHost.removeTile(BRIGHTNESS);
+ mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
+ }
+ }
+
private void addReduceBrightColorsTile() {
if (mAutoTracker.isAdded(BRIGHTNESS)) return;
mHost.addTile(BRIGHTNESS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 97b6f95..6f29f61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -53,11 +53,14 @@
import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -327,14 +330,17 @@
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
javaAdapter.alwaysCollectFlow(
- keyguardTransitionInteractor.getStartedKeyguardTransitionStep(),
- this::consumeTransitionStepOnStartedKeyguardState);
+ keyguardTransitionInteractor.transition(
+ /* edge */ Edge.create(Scenes.Gone, null),
+ /* edgeWithoutSceneContainer */ Edge.create(
+ KeyguardState.GONE, (KeyguardState) null)),
+ this::consumeFromGoneTransitions);
dumpManager.registerDumpable(this);
}
@VisibleForTesting
- protected void consumeTransitionStepOnStartedKeyguardState(TransitionStep transitionStep) {
- if (transitionStep.getFrom() == KeyguardState.GONE) {
+ protected void consumeFromGoneTransitions(TransitionStep transitionStep) {
+ if (transitionStep.getTransitionState() == TransitionState.STARTED) {
mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE, null);
}
}
@@ -363,10 +369,12 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
+ Trace.beginSection("release wake-and-unlock");
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
+ Trace.endSection();
}
}
@@ -392,7 +400,7 @@
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
- Trace.beginSection("acquiring wake-and-unlock");
+ Trace.beginSection("acquire wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
mLogger.i("biometric acquired, grabbing biometric wakelock");
@@ -406,14 +414,13 @@
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricDetected");
- if (mUpdateMonitor.isGoingToSleep()) {
- Trace.endSection();
- return;
+ if (!mUpdateMonitor.isGoingToSleep()) {
+ startWakeAndUnlock(
+ MODE_SHOW_BOUNCER,
+ BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+ );
}
- startWakeAndUnlock(
- MODE_SHOW_BOUNCER,
- BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
- );
+ Trace.endSection();
}
@Override
@@ -445,6 +452,7 @@
} else {
mLogger.d("onBiometricUnlocked aborted by bypass controller");
}
+ Trace.endSection();
}
/**
@@ -473,6 +481,7 @@
@WakeAndUnlockMode int mode,
BiometricUnlockSource biometricUnlockSource
) {
+ Trace.beginSection("BiometricUnlockController#startWakeAndUnlock");
mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
@@ -495,9 +504,7 @@
"android.policy:BIOMETRIC"
);
}
- Trace.beginSection("release wake-and-unlock");
releaseBiometricWakeLock();
- Trace.endSection();
};
final boolean wakeInKeyguard = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index bd0097e..398c1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -74,9 +74,9 @@
mLightModeIconColorSingleTone = Color.WHITE;
} else {
mDarkModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.black);
+ com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
mLightModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.white);
+ com.android.settingslib.R.color.light_mode_icon_color_single_tone);
}
mTransitionsController = lightBarTransitionsControllerFactory.create(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a11cbc3..98869be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,6 +37,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,6 +55,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Optional;
@@ -86,6 +88,7 @@
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final UserTracker mUserTracker;
+ private final SecureSettings mSecureSettings;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
@@ -130,7 +133,8 @@
ConfigurationController configurationController,
StatusBarStateController statusBarStateController,
UserTracker userTracker,
- DozeInteractor dozeInteractor) {
+ DozeInteractor dozeInteractor,
+ SecureSettings secureSettings) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +148,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mUserTracker = userTracker;
mDozeInteractor = dozeInteractor;
+ mSecureSettings = secureSettings;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
@@ -160,7 +165,8 @@
mFoldAodAnimationController.addCallback(this);
}
- SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler);
+ SettingsObserver quickPickupSettingsObserver =
+ new SettingsObserver(context, handler, mSecureSettings);
quickPickupSettingsObserver.observe();
batteryController.addCallback(new BatteryStateChangeCallback() {
@@ -479,18 +485,36 @@
Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON);
private final Context mContext;
- SettingsObserver(Context context, Handler handler) {
+ private final Handler mHandler;
+ private final SecureSettings mSecureSettings;
+
+ SettingsObserver(Context context, Handler handler, SecureSettings secureSettings) {
super(handler);
mContext = context;
+ mHandler = handler;
+ mSecureSettings = secureSettings;
}
void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(mQuickPickupGesture, false, this,
- UserHandle.USER_ALL);
- resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(mAlwaysOnEnabled, false, this, UserHandle.USER_ALL);
- update(null);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(mQuickPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mAlwaysOnEnabled,
+ this, UserHandle.USER_ALL,
+ // The register calls are called in order, so this ensures that update()
+ // is called after them all and value retrieval isn't racy.
+ () -> mHandler.post(() -> update(null)));
+ } else {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mQuickPickupGesture, false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(mAlwaysOnEnabled, false, this,
+ UserHandle.USER_ALL);
+ update(null);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 330383f..a32d5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -29,8 +29,10 @@
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
+import com.android.app.tracing.TraceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.assist.AssistManager;
@@ -61,6 +63,8 @@
import dagger.Lazy;
+import kotlin.Unit;
+
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import javax.inject.Inject;
@@ -109,7 +113,7 @@
private CentralSurfaces mCentralSurfaces;
private boolean mAlwaysOnSuppressed;
private boolean mPulsePending;
- private DozeInteractor mDozeInteractor;
+ private final DozeInteractor mDozeInteractor;
@Inject
public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -337,13 +341,17 @@
}
@Override
+ @MainThread
public void dozeTimeTick() {
- mDozeInteractor.dozeTimeTick();
- mShadeLockscreenInteractor.dozeTimeTick();
- mAuthController.dozeTimeTick();
- if (mAmbientIndicationContainer instanceof DozeReceiver) {
- ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
- }
+ TraceUtils.trace("DozeServiceHost#dozeTimeTick", () -> {
+ mDozeInteractor.dozeTimeTick();
+ mShadeLockscreenInteractor.dozeTimeTick();
+ mAuthController.dozeTimeTick();
+ if (mAmbientIndicationContainer instanceof DozeReceiver) {
+ ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+ }
+ return Unit.INSTANCE;
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0623bb2c..a2e44df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,7 +31,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -40,7 +39,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
@@ -88,7 +86,7 @@
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
private final VisualStabilityProvider mVisualStabilityProvider;
- private AvalancheController mAvalancheController;
+ private final AvalancheController mAvalancheController;
// TODO(b/328393698) move the topHeadsUpRow logic to an interactor
private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -173,11 +171,8 @@
updateResources();
}
});
- if (!NotificationsHeadsUpRefactor.isEnabled()) {
- javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
this::onShadeOrQsExpanded);
- }
- mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener);
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -272,10 +267,9 @@
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
- NotificationsHeadsUpRefactor.assertInLegacyMode();
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
- if (isExpanded) {
+ if (!NotificationsHeadsUpRefactor.isEnabled() && isExpanded) {
mHeadsUpAnimatingAway.setValue(false);
}
}
@@ -385,8 +379,6 @@
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
- mAvalancheController.setEnableAtRuntime(true);
-
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
@@ -397,29 +389,6 @@
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
};
- private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
- if (mAvalancheController != null) {
- // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus
- // seen in open shade; clear them so we don't show them again when the shade closes and
- // reordering is allowed again.
- final int numDropped = mAvalancheController.getWaitingKeys().size();
- mAvalancheController.logDroppedHunsInBackground(numDropped);
- mAvalancheController.clearNext();
-
- // In open shade the first HUN is pinned, and visual stability logic prevents us from
- // unpinning this first HUN as long as the shade remains open. AvalancheController only
- // shows the next HUN when the currently showing HUN is unpinned, so we must disable
- // throttling here so that the incoming HUN stream is not forever paused. This is reset
- // when reorder becomes allowed.
- mAvalancheController.setEnableAtRuntime(false);
-
- // Note that we cannot do the above when
- // 1) the remove runnable runs because its delay means it may not run before shade close
- // 2) reordering is allowed again (when shade closes) because the HUN appear animation
- // will have started by then
- }
- };
-
///////////////////////////////////////////////////////////////////////////////////////////////
// HeadsUpManager utility (protected) methods overrides:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 7f16e18..26bd7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -124,7 +124,6 @@
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpand(x, y, true /* startTracking */, startHeight);
- // TODO(b/340514839): Figure out where to move this side effect in flexiglass
if (!SceneContainerFlag.isEnabled()) {
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 97791ac..316e1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import com.android.app.tracing.ListenersTracing.forEachTraced
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -159,7 +160,7 @@
}
fun listenForQsExpandedChange() =
- applicationScope.launch {
+ applicationScope.launch("listenForQsExpandedChange") {
shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
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 d0a62e7..84e6018 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -455,8 +455,8 @@
float luminance = Color.luminance(textColor);
@ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
luminance < 0.5
- ? com.android.settingslib.R.color.black
- : com.android.settingslib.R.color.white);
+ ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+ : com.android.settingslib.R.color.light_mode_icon_color_single_tone);
@ColorInt int contrastColor = luminance < 0.5
? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
: DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
@@ -467,7 +467,7 @@
if (userSwitcherName != null) {
userSwitcherName.setTextColor(Utils.getColorStateListDefaultColor(
mContext,
- com.android.settingslib.R.color.white));
+ com.android.settingslib.R.color.light_mode_icon_color_single_tone));
}
if (iconManager != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index bcb613f..de76b10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -276,10 +276,11 @@
statusBarController
}
+ val isCommunalDismissLaunch = isCommunalWidgetLaunch() && !actuallyShowOverLockscreen
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
- val collapse = dismissShade && !animate
+ val collapse = (dismissShade || isCommunalDismissLaunch) && !animate
val runnable = Runnable {
try {
activityTransitionAnimator.startPendingIntentWithAnimation(
@@ -338,8 +339,9 @@
postOnUiThread(delay = 0) {
executeRunnableDismissingKeyguard(
runnable = runnable,
- afterKeyguardGone = willLaunchResolverActivity,
dismissShade = collapse,
+ afterKeyguardGone = willLaunchResolverActivity,
+ deferred = isCommunalDismissLaunch,
willAnimateOnKeyguard = animate,
customMessage = customMessage,
)
@@ -461,7 +463,9 @@
override fun onDismiss(): Boolean {
if (runnable != null) {
if (
- keyguardStateController.isShowing && keyguardStateController.isOccluded
+ keyguardStateController.isShowing &&
+ keyguardStateController.isOccluded &&
+ !isCommunalWidgetLaunch()
) {
statusBarKeyguardViewManagerLazy
.get()
@@ -473,17 +477,10 @@
if (dismissShade) {
shadeControllerLazy.get().collapseShadeForActivityStart()
}
- if (communalHub()) {
- communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
- }
return deferred
}
override fun willRunAnimationOnKeyguard(): Boolean {
- if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
- // Override to false when launching activity over the hub that requires auth
- return false
- }
return willAnimateOnKeyguard
}
}
@@ -639,7 +636,8 @@
showOverLockscreen: Boolean,
): Boolean {
// TODO(b/294418322): always support launch animations when occluded.
- val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation()
+ val ignoreOcclusion =
+ (showOverLockscreen && mediaLockscreenLaunchAnimation()) || isCommunalWidgetLaunch()
if (keyguardStateController.isOccluded && !ignoreOcclusion) {
return false
}
@@ -659,6 +657,12 @@
return shouldAnimateLaunch(isActivityIntent, false)
}
+ private fun isCommunalWidgetLaunch(): Boolean {
+ return communalHub() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ communalSceneInteractor.isLaunchingWidget.value
+ }
+
private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
mainExecutor.executeDelayed(runnable, delay.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 824415e..231a8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -39,7 +39,7 @@
) {
override fun toString(): String {
val appearanceString =
- ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
}
}
@@ -57,16 +57,14 @@
private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
) : Dumpable {
- private val darkAppearanceIconColor =
- context.getColor(
- // For a dark background status bar, use a *light* icon color.
- com.android.settingslib.R.color.white
- )
- private val lightAppearanceIconColor =
- context.getColor(
- // For a light background status bar, use a *dark* icon color.
- com.android.settingslib.R.color.black
- )
+ private val darkAppearanceIconColor = context.getColor(
+ // For a dark background status bar, use a *light* icon color.
+ com.android.settingslib.R.color.light_mode_icon_color_single_tone
+ )
+ private val lightAppearanceIconColor = context.getColor(
+ // For a light background status bar, use a *dark* icon color.
+ com.android.settingslib.R.color.dark_mode_icon_color_single_tone
+ )
init {
dumpManager.registerCriticalDumpable(this)
@@ -87,11 +85,7 @@
lastAppearanceRegions = originalAppearanceRegions
lastLetterboxes = letterboxes
return getLetterboxAppearanceInternal(
- letterboxes,
- originalAppearance,
- originalAppearanceRegions,
- statusBarBounds
- )
+ letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
.also { lastLetterboxAppearance = it }
}
@@ -144,9 +138,7 @@
// full bounds of its window.
// Here we want the bounds to be only for the inner bounds of the letterboxed app.
AppearanceRegion(
- appearanceRegion.appearance,
- matchingLetterbox.letterboxInnerBounds
- )
+ appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
}
}
@@ -156,8 +148,7 @@
): LetterboxAppearance {
return LetterboxAppearance(
originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
- originalAppearanceRegions
- )
+ originalAppearanceRegions)
}
@Appearance
@@ -224,9 +215,7 @@
lastAppearanceRegion: $lastAppearanceRegions,
lastLetterboxes: $lastLetterboxes,
lastLetterboxAppearance: $lastLetterboxAppearance
- """
- .trimIndent()
- )
+ """.trimIndent())
}
}
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 e4edfa4..069c624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -474,6 +474,7 @@
// PRIMARY_BOUNCER->GONE
collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.getINVALID(),
Edge.Companion.create(PRIMARY_BOUNCER, GONE)),
mBouncerToGoneTransition, mMainDispatcher);
collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 507759c..68163b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -325,7 +325,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), true)
- private val shownLevel: StateFlow<Int> =
+ private val cellularShownLevel: StateFlow<Int> =
combine(
level,
isInService,
@@ -337,15 +337,19 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+ // Satellite level is unaffected by the isInService or inflateSignalStrength properties
+ // See b/346904529 for details
+ private val satelliteShownLevel: StateFlow<Int> = level
+
private val cellularIcon: Flow<SignalIconModel.Cellular> =
combine(
- shownLevel,
+ cellularShownLevel,
numberOfLevels,
showExclamationMark,
carrierNetworkChangeActive,
- ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ ) { cellularShownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
SignalIconModel.Cellular(
- shownLevel,
+ cellularShownLevel,
numberOfLevels,
showExclamationMark,
carrierNetworkChange,
@@ -353,7 +357,7 @@
}
private val satelliteIcon: Flow<SignalIconModel.Satellite> =
- shownLevel.map {
+ satelliteShownLevel.map {
SignalIconModel.Satellite(
level = it,
icon =
@@ -365,7 +369,7 @@
override val signalLevelIcon: StateFlow<SignalIconModel> = run {
val initial =
SignalIconModel.Cellular(
- shownLevel.value,
+ cellularShownLevel.value,
numberOfLevels.value,
showExclamationMark.value,
carrierNetworkChangeActive.value,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 8aabdf2..43ab337 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -44,7 +44,6 @@
private val tag = "AvalancheController"
private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
- var enableAtRuntime = true
// HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
@VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -87,17 +86,13 @@
dumpManager.registerNormalDumpable(tag, /* module */ this)
}
- fun isEnabled() : Boolean {
- return NotificationThrottleHun.isEnabled && enableAtRuntime
- }
-
fun getShowingHunKey(): String {
return getKey(headsUpEntryShowing)
}
/** Run or delay Runnable for given HeadsUpEntry */
fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
}
@@ -153,7 +148,7 @@
* all Runnables associated with that entry.
*/
fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
}
@@ -194,7 +189,7 @@
* BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
*/
fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
}
@@ -243,7 +238,7 @@
/** Return true if entry is waiting to show. */
fun isWaiting(key: String): Boolean {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
return false
}
for (entry in nextMap.keys) {
@@ -256,7 +251,7 @@
/** Return list of keys for huns waiting */
fun getWaitingKeys(): MutableList<String> {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
return mutableListOf()
}
val keyList = mutableListOf<String>()
@@ -267,7 +262,7 @@
}
fun getWaitingEntry(key: String): HeadsUpEntry? {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
return null
}
for (headsUpEntry in nextMap.keys) {
@@ -279,7 +274,7 @@
}
fun getWaitingEntryList(): List<HeadsUpEntry> {
- if (!isEnabled()) {
+ if (!NotificationThrottleHun.isEnabled) {
return mutableListOf()
}
return nextMap.keys.toList()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 45cb52a..7b82b56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -26,7 +26,6 @@
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.util.ArrayMap;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -46,11 +45,11 @@
/** Platform implementation of the cast controller. **/
@SysUISingleton
public class CastControllerImpl implements CastController {
- public static final String TAG = "CastController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String TAG = "CastController";
private final Context mContext;
private final PackageManager mPackageManager;
+ private final CastControllerLogger mLogger;
@GuardedBy("mCallbacks")
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final MediaRouter mMediaRouter;
@@ -67,20 +66,22 @@
public CastControllerImpl(
Context context,
PackageManager packageManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ CastControllerLogger logger) {
mContext = context;
mPackageManager = packageManager;
+ mLogger = logger;
mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouter.setRouterGroupId(MediaRouter.MIRRORING_GROUP_ID);
mProjectionManager = (MediaProjectionManager)
context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mProjection = mProjectionManager.getActiveProjectionInfo();
mProjectionManager.addCallback(mProjectionCallback, new Handler());
- dumpManager.registerDumpable(TAG, this);
- if (DEBUG) Log.d(TAG, "new CastController()");
+ dumpManager.registerNormalDumpable(TAG, this);
}
- public void dump(PrintWriter pw, String[] args) {
+ @Override
+ public void dump(PrintWriter pw, @NonNull String[] args) {
pw.println("CastController state:");
pw.print(" mDiscovering="); pw.println(mDiscovering);
pw.print(" mCallbackRegistered="); pw.println(mCallbackRegistered);
@@ -88,7 +89,7 @@
pw.print(" mRoutes.size="); pw.println(mRoutes.size());
for (int i = 0; i < mRoutes.size(); i++) {
final RouteInfo route = mRoutes.valueAt(i);
- pw.print(" "); pw.println(routeToString(route));
+ pw.print(" "); pw.println(CastControllerLogger.Companion.toLogString(route));
}
pw.print(" mProjection="); pw.println(mProjection);
}
@@ -119,7 +120,7 @@
synchronized (mDiscoveringLock) {
if (mDiscovering == request) return;
mDiscovering = request;
- if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
+ mLogger.logDiscovering(request);
handleDiscoveryChangeLocked();
}
}
@@ -166,7 +167,8 @@
CastDevice.Companion.toCastDevice(
mProjection,
mContext,
- mPackageManager));
+ mPackageManager,
+ mLogger));
}
}
@@ -177,22 +179,24 @@
public void startCasting(CastDevice device) {
if (device == null || device.getTag() == null) return;
final RouteInfo route = (RouteInfo) device.getTag();
- if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
+ mLogger.logStartCasting(route);
mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
}
@Override
public void stopCasting(CastDevice device) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
- if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
+ mLogger.logStopCasting(isProjection);
if (isProjection) {
final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();
if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
mProjectionManager.stopActiveProjection();
} else {
- Log.w(TAG, "Projection is no longer active: " + projection);
+ mLogger.logStopCastingNoProjection(projection);
}
} else {
+ mLogger.logStopCastingMediaRouter();
mMediaRouter.getFallbackRoute().select();
}
}
@@ -217,7 +221,7 @@
}
}
if (changed) {
- if (DEBUG) Log.d(TAG, "setProjection: " + oldProjection + " -> " + mProjection);
+ mLogger.logSetProjection(oldProjection, mProjection);
fireOnCastDevicesChanged();
}
}
@@ -264,42 +268,30 @@
callback.onCastDevicesChanged();
}
- private static String routeToString(RouteInfo route) {
- if (route == null) return null;
- final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
- .append(route.getDescription()).append('@').append(route.getDeviceAddress())
- .append(",status=").append(route.getStatus());
- if (route.isDefault()) sb.append(",default");
- if (route.isEnabled()) sb.append(",enabled");
- if (route.isConnecting()) sb.append(",connecting");
- if (route.isSelected()) sb.append(",selected");
- return sb.append(",id=").append(route.getTag()).toString();
- }
-
private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
+ mLogger.logRouteAdded(route);
updateRemoteDisplays();
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
+ mLogger.logRouteChanged(route);
updateRemoteDisplays();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
+ mLogger.logRouteRemoved(route);
updateRemoteDisplays();
}
@Override
public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
+ mLogger.logRouteSelected(route, type);
updateRemoteDisplays();
}
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
- if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
+ mLogger.logRouteUnselected(route, type);
updateRemoteDisplays();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt
new file mode 100644
index 0000000..9a3a244
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerLogger.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.media.MediaRouter.RouteInfo
+import android.media.projection.MediaProjectionInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.statusbar.policy.dagger.CastControllerLog
+import javax.inject.Inject
+
+/** Helper class for logging events to [CastControllerLog] from Java. */
+@SysUISingleton
+class CastControllerLogger
+@Inject
+constructor(
+ @CastControllerLog val logger: LogBuffer,
+) {
+ /** Passthrough to [logger]. */
+ inline fun log(
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
+ ) {
+ logger.log(tag, level, messageInitializer, messagePrinter, exception)
+ }
+
+ fun logDiscovering(isDiscovering: Boolean) =
+ logger.log(TAG, LogLevel.DEBUG, { bool1 = isDiscovering }, { "setDiscovering: $bool1" })
+
+ fun logStartCasting(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "startCasting: $str1" })
+
+ fun logStopCasting(isProjection: Boolean) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isProjection },
+ { "stopCasting. isProjection=$bool1" },
+ )
+
+ fun logStopCastingNoProjection(projection: MediaProjectionInfo) =
+ logger.log(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = projection.toString() },
+ { "stopCasting failed because projection is no longer active: $str1" },
+ )
+
+ fun logStopCastingMediaRouter() =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {},
+ { "stopCasting is selecting fallback route in MediaRouter" },
+ )
+
+ fun logSetProjection(oldInfo: MediaProjectionInfo?, newInfo: MediaProjectionInfo?) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = oldInfo.toString()
+ str2 = newInfo.toString()
+ },
+ { "setProjection: $str1 -> $str2" },
+ )
+
+ fun logRouteAdded(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteAdded: $str1" })
+
+ fun logRouteChanged(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteChanged: $str1" })
+
+ fun logRouteRemoved(route: RouteInfo) =
+ logger.log(TAG, LogLevel.DEBUG, { str1 = route.toLogString() }, { "onRouteRemoved: $str1" })
+
+ fun logRouteSelected(route: RouteInfo, type: Int) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = route.toLogString()
+ int1 = type
+ },
+ { "onRouteSelected($int1): $str1" },
+ )
+
+ fun logRouteUnselected(route: RouteInfo, type: Int) =
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = route.toLogString()
+ int1 = type
+ },
+ { "onRouteUnselected($int1): $str1" },
+ )
+
+ companion object {
+ @JvmStatic
+ fun RouteInfo?.toLogString(): String? {
+ if (this == null) return null
+ val sb =
+ StringBuilder()
+ .append(name)
+ .append('/')
+ .append(description)
+ .append('@')
+ .append(deviceAddress)
+ .append(",status=")
+ .append(status)
+ if (isDefault) sb.append(",default")
+ if (isEnabled) sb.append(",enabled")
+ if (isConnecting) sb.append(",connecting")
+ if (isSelected) sb.append(",selected")
+ return sb.append(",id=").append(this.tag).toString()
+ }
+
+ private const val TAG = "CastController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
index 5fc160b..a787f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
@@ -20,7 +20,7 @@
import android.media.MediaRouter
import android.media.projection.MediaProjectionInfo
import android.text.TextUtils
-import android.util.Log
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.util.Utils
@@ -38,6 +38,9 @@
) {
val isCasting = state == CastState.Connecting || state == CastState.Connected
+ val shortLogString: String =
+ "CastDevice(id=$id name=$name description=$description state=$state origin=$origin)"
+
companion object {
/** Creates a [CastDevice] based on the provided information from MediaRouter. */
fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice {
@@ -61,11 +64,12 @@
/** Creates a [CastDevice] based on the provided information from MediaProjection. */
fun MediaProjectionInfo.toCastDevice(
context: Context,
- packageManager: PackageManager
+ packageManager: PackageManager,
+ logger: CastControllerLogger,
): CastDevice {
return CastDevice(
id = this.packageName,
- name = getAppName(this.packageName, packageManager),
+ name = getAppName(this.packageName, packageManager, logger),
description = context.getString(R.string.quick_settings_casting),
state = CastState.Connected,
tag = this,
@@ -73,7 +77,11 @@
)
}
- private fun getAppName(packageName: String, packageManager: PackageManager): String {
+ private fun getAppName(
+ packageName: String,
+ packageManager: PackageManager,
+ logger: CastControllerLogger,
+ ): String {
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)) {
return ""
}
@@ -83,9 +91,20 @@
if (!TextUtils.isEmpty(label)) {
return label.toString()
}
- Log.w(CastControllerImpl.TAG, "No label found for package: $packageName")
+ logger.log(
+ "#getAppName",
+ LogLevel.WARNING,
+ { str1 = packageName },
+ { "No label found for package: $str1" },
+ )
} catch (e: PackageManager.NameNotFoundException) {
- Log.w(CastControllerImpl.TAG, "Error getting appName for package: $packageName", e)
+ logger.log(
+ "#getAppName",
+ LogLevel.WARNING,
+ { str1 = packageName },
+ { "Error getting appName for package=$str1" },
+ e,
+ )
}
return packageName
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 2ce2bc8..81e41d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -45,6 +45,10 @@
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
@@ -85,35 +89,35 @@
@IntoMap
@StringKey(FLASHLIGHT_TILE_SPEC)
fun provideAirplaneModeAvailabilityInteractor(
- impl: FlashlightTileDataInteractor
+ impl: FlashlightTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(LOCATION_TILE_SPEC)
fun provideLocationAvailabilityInteractor(
- impl: LocationTileDataInteractor
+ impl: LocationTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(ALARM_TILE_SPEC)
fun provideAlarmAvailabilityInteractor(
- impl: AlarmTileDataInteractor
+ impl: AlarmTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(UIMODENIGHT_TILE_SPEC)
fun provideUiModeNightAvailabilityInteractor(
- impl: UiModeNightTileDataInteractor
+ impl: UiModeNightTileDataInteractor
): QSTileAvailabilityInteractor
@Binds
@IntoMap
@StringKey(WORK_MODE_TILE_SPEC)
fun provideWorkModeAvailabilityInteractor(
- impl: WorkModeTileDataInteractor
+ impl: WorkModeTileDataInteractor
): QSTileAvailabilityInteractor
companion object {
@@ -125,6 +129,7 @@
const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
const val DND_TILE_SPEC = "dnd"
+ const val MODES_TILE_SPEC = "modes"
/** Inject flashlight config */
@Provides
@@ -327,7 +332,7 @@
@IntoMap
@StringKey(CAMERA_TOGGLE_TILE_SPEC)
fun provideCameraToggleAvailabilityInteractor(
- factory: SensorPrivacyToggleTileDataInteractor.Factory
+ factory: SensorPrivacyToggleTileDataInteractor.Factory
): QSTileAvailabilityInteractor {
return factory.create(CAMERA)
}
@@ -369,12 +374,11 @@
@IntoMap
@StringKey(MIC_TOGGLE_TILE_SPEC)
fun provideMicToggleModeAvailabilityInteractor(
- factory: SensorPrivacyToggleTileDataInteractor.Factory
+ factory: SensorPrivacyToggleTileDataInteractor.Factory
): QSTileAvailabilityInteractor {
return factory.create(MICROPHONE)
}
-
/** Inject microphone toggle config */
@Provides
@IntoMap
@@ -389,6 +393,37 @@
),
instanceId = uiEventLogger.getNewInstanceId(),
)
+
+ @Provides
+ @IntoMap
+ @StringKey(MODES_TILE_SPEC)
+ fun provideModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(MODES_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_modes_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject ModesTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(MODES_TILE_SPEC)
+ fun provideModesTileViewModel(
+ factory: QSTileViewModelFactory.Static<ModesTileModel>,
+ mapper: ModesTileMapper,
+ stateInteractor: ModesTileDataInteractor,
+ userActionInteractor: ModesTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(MODES_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
index d120a1b..72d093c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
@@ -22,10 +22,10 @@
/** Returns true if the device should use the split notification shade. */
@Deprecated(
- message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+ message = "This is deprecated, please use ShadeInteractor#shadeMode instead",
replaceWith =
ReplaceWith(
- "shadeInteractor.isSplitShade",
+ "shadeInteractor.shadeMode",
"com.android.systemui.shade.domain.interactor.ShadeInteractor",
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt
index c92234c..23aade6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/CastControllerLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.statusbar.policy.dagger
import javax.inject.Qualifier
+/**
+ * Logs for cast events. See [com.android.systemui.statusbar.policy.CastControllerImpl] and
+ * [com.android.systemui.statusbar.policy.CastControllerLogger].
+ */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class IconLabelVisibilityLog()
+annotation class CastControllerLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index e08e4d7..71bcdfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -228,4 +228,12 @@
static LogBuffer provideBatteryControllerLog(LogBufferFactory factory) {
return factory.create(BatteryControllerLogger.TAG, 30);
}
+
+ /** Provides a log buffer for CastControllerImpl */
+ @Provides
+ @SysUISingleton
+ @CastControllerLog
+ static LogBuffer provideCastControllerLog(LogBufferFactory factory) {
+ return factory.create("CastControllerLog", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
deleted file mode 100644
index d54c07c..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.tuner.TunerService.Tunable;
-
-import javax.inject.Inject;
-
-/**
- * Version of Space that can be resized by a tunable setting.
- */
-public class TunablePadding implements Tunable {
-
- public static final int FLAG_START = 1;
- public static final int FLAG_END = 2;
- public static final int FLAG_TOP = 4;
- public static final int FLAG_BOTTOM = 8;
-
- private final int mFlags;
- private final View mView;
- private final int mDefaultSize;
- private final float mDensity;
- private final TunerService mTunerService;
-
- private TunablePadding(String key, int def, int flags, View view, TunerService tunerService) {
- mDefaultSize = def;
- mFlags = flags;
- mView = view;
- DisplayMetrics metrics = new DisplayMetrics();
- view.getContext().getSystemService(WindowManager.class)
- .getDefaultDisplay().getMetrics(metrics);
- mDensity = metrics.density;
- mTunerService = tunerService;
- mTunerService.addTunable(this, key);
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- int dimen = mDefaultSize;
- if (newValue != null) {
- try {
- dimen = (int) (Integer.parseInt(newValue) * mDensity);
- } catch (NumberFormatException ex) {}
- }
- int left = mView.isLayoutRtl() ? FLAG_END : FLAG_START;
- int right = mView.isLayoutRtl() ? FLAG_START : FLAG_END;
- mView.setPadding(getPadding(dimen, left), getPadding(dimen, FLAG_TOP),
- getPadding(dimen, right), getPadding(dimen, FLAG_BOTTOM));
- }
-
- private int getPadding(int dimen, int flag) {
- return ((mFlags & flag) != 0) ? dimen : 0;
- }
-
- public void destroy() {
- mTunerService.removeTunable(this);
- }
-
- /**
- * Exists for easy injecting in tests.
- */
- @SysUISingleton
- public static class TunablePaddingService {
-
- private final TunerService mTunerService;
-
- /**
- */
- @Inject
- public TunablePaddingService(TunerService tunerService) {
- mTunerService = tunerService;
- }
-
- public TunablePadding add(View view, String key, int defaultSize, int flags) {
- if (view == null) {
- throw new IllegalArgumentException();
- }
- return new TunablePadding(key, defaultSize, flags, view, mTunerService);
- }
- }
-
- public static TunablePadding addTunablePadding(View view, String key, int defaultSize,
- int flags) {
- return Dependency.get(TunablePaddingService.class).add(view, key, defaultSize, flags);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index ec7aabb..f2132248 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -20,6 +20,7 @@
import android.util.IndentingPrintWriter
import android.view.View
import android.view.ViewGroup
+import dagger.Lazy
import java.io.PrintWriter
/** [Sequence] that yields all of the direct children of this [ViewGroup] */
@@ -56,3 +57,8 @@
getBoundsOnScreen(bounds)
return bounds
}
+
+/** Extension method to convert [dagger.Lazy] to [kotlin.Lazy] for object of any class [T]. */
+fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> {
+ return lazy { this.get() }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
index ee00e8b..e6e2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -35,3 +35,17 @@
}
.onStart { emit(isReduceBrightColorsActivated) }
}
+
+fun ReduceBrightColorsController.isAvailable(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : ReduceBrightColorsController.Listener {
+ override fun onFeatureEnabledChanged(enabled: Boolean) {
+ trySend(enabled)
+ }
+ }
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+ .onStart { emit(isReduceBrightColorsFeatureAvailable) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index fe54044..b5934ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -19,6 +19,7 @@
import android.database.ContentObserver
import android.net.Uri
import android.provider.Settings.SettingNotFoundException
+import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
import com.android.app.tracing.TraceUtils.trace
import kotlinx.coroutines.CoroutineDispatcher
@@ -57,7 +58,7 @@
* @param name to look up in the table
* @return the corresponding content URI, or null if not present
*/
- fun getUriFor(name: String): Uri
+ @AnyThread fun getUriFor(name: String): Uri
/**
* Registers listener for a given content observer <b>while blocking the current thread</b>.
@@ -89,12 +90,31 @@
*
* API corresponding to [registerContentObserver] for Java usage.
*/
+ @AnyThread
fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
CoroutineScope(backgroundDispatcher).launch {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserver] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ @AnyThread
+ fun registerContentObserverAsync(
+ name: String,
+ settingsObserver: ContentObserver,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverSync(getUriFor(name), settingsObserver)
+ registered.run()
+ }
+
+ /**
* Registers listener for a given content observer <b>while blocking the current thread</b>.
*
* This should not be called from the main thread, use [registerContentObserver] or
@@ -120,6 +140,7 @@
*
* API corresponding to [registerContentObserver] for Java usage.
*/
+ @AnyThread
fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
CoroutineScope(backgroundDispatcher).launch {
registerContentObserverSync(uri, settingsObserver)
@@ -128,8 +149,27 @@
/**
* Convenience wrapper around [ContentResolver.registerContentObserver].'
*
+ * API corresponding to [registerContentObserver] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ @AnyThread
+ fun registerContentObserverAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverSync(uri, settingsObserver)
+ registered.run()
+ }
+
+ /**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverSync(
name: String,
notifyForDescendants: Boolean,
@@ -158,6 +198,7 @@
*
* API corresponding to [registerContentObserver] for Java usage.
*/
+ @AnyThread
fun registerContentObserverAsync(
name: String,
notifyForDescendants: Boolean,
@@ -168,6 +209,25 @@
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserver] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ @AnyThread
+ fun registerContentObserverAsync(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+ registered.run()
+ }
+
+ /**
* Registers listener for a given content observer <b>while blocking the current thread</b>.
*
* This should not be called from the main thread, use [registerContentObserver] or
@@ -207,6 +267,7 @@
*
* API corresponding to [registerContentObserver] for Java usage.
*/
+ @AnyThread
fun registerContentObserverAsync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -217,6 +278,25 @@
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserver] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ @AnyThread
+ fun registerContentObserverAsync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
+ registered.run()
+ }
+
+ /**
* Unregisters the given content observer <b>while blocking the current thread</b>.
*
* This should not be called from the main thread, use [unregisterContentObserver] or
@@ -246,6 +326,7 @@
* API corresponding to [unregisterContentObserver] for Java usage to ensure that
* [ContentObserver] registration happens on a worker thread.
*/
+ @AnyThread
fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 3bf5b65..848a6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util.settings
import android.annotation.UserIdInt
+import android.annotation.WorkerThread
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
@@ -66,6 +67,7 @@
} else userTracker.userId
}
+ @WorkerThread
override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -82,6 +84,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ @WorkerThread
override fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -119,6 +122,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
@@ -159,6 +163,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
@@ -199,10 +204,29 @@
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserverForUser] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ registered.run()
+ }
+
+ /**
* Convenience wrapper around [ContentResolver.registerContentObserver]
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
@@ -262,6 +286,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index bd698ab..bb230e6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -30,21 +30,31 @@
import android.media.AudioManager;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.view.KeyEvent;
import android.view.WindowManager;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.Flags;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.google.common.collect.ImmutableList;
+
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import java.util.Optional;
+
/**
* A class that implements the three Computed Sound Dose-related warnings defined in
* {@link AudioManager}:
@@ -75,6 +85,9 @@
private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
// time after which action is taken when the user hasn't ack'd or dismissed the dialog
public static final int NO_ACTION_TIMEOUT_MS = 5000;
+ // Notification dismiss intent
+ private static final String DISMISS_CSD_NOTIFICATION =
+ "com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
private final Context mContext;
private final AudioManager mAudioManager;
@@ -95,21 +108,32 @@
private long mShowTime;
+ @VisibleForTesting public int mCachedMediaStreamVolume;
+ private Optional<ImmutableList<Pair<String, Intent>>> mActionIntents;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
/**
* To inject dependencies and allow for easier testing
*/
@AssistedFactory
public interface Factory {
- /**
- * Create a dialog object
- */
- CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+ /** Create a dialog object */
+ CsdWarningDialog create(
+ int csdWarning,
+ Runnable onCleanup,
+ Optional<ImmutableList<Pair<String, Intent>>> actionIntents);
}
@AssistedInject
- public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
- AudioManager audioManager, NotificationManager notificationManager,
- @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
+ public CsdWarningDialog(
+ @Assisted @AudioManager.CsdWarning int csdWarning,
+ Context context,
+ AudioManager audioManager,
+ NotificationManager notificationManager,
+ @Background DelayableExecutor delayableExecutor,
+ @Assisted Runnable onCleanup,
+ @Assisted Optional<ImmutableList<Pair<String, Intent>>> actionIntents,
+ BroadcastDispatcher broadcastDispatcher) {
super(context);
mCsdWarning = csdWarning;
mContext = context;
@@ -118,7 +142,8 @@
mOnCleanup = onCleanup;
mDelayableExecutor = delayableExecutor;
-
+ mActionIntents = actionIntents;
+ mBroadcastDispatcher = broadcastDispatcher;
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
setShowForAllUsers(true);
setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -133,14 +158,17 @@
Context.RECEIVER_EXPORTED_UNAUDITED);
if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
- mNoUserActionRunnable = () -> {
- if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
- // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
- // is not acknowledged quickly enough
- mAudioManager.lowerVolumeToRs1();
- sendNotification(/*for5XCsd=*/false);
- }
- };
+ mNoUserActionRunnable =
+ () -> {
+ if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+ // unlike on the 5x dose repeat, level is only reduced to RS1 when the
+ // warning is not acknowledged quickly enough
+ mCachedMediaStreamVolume =
+ mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.lowerVolumeToRs1();
+ sendNotification(/* for5XCsd= */ false);
+ }
+ };
} else {
mNoUserActionRunnable = null;
}
@@ -242,6 +270,38 @@
}
};
+ @VisibleForTesting
+ public final BroadcastReceiver mReceiverUndo =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Flags.sounddoseCustomization()
+ && VolumeDialog.ACTION_VOLUME_UNDO.equals(intent.getAction())) {
+ if (D.BUG) Log.d(TAG, "Received ACTION_VOLUME_UNDO");
+ mAudioManager.setStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ mCachedMediaStreamVolume,
+ AudioManager.FLAG_SHOW_UI);
+ mNotificationManager.cancel(
+ SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO);
+ destroy();
+ }
+ }
+ };
+
+ @VisibleForTesting
+ public final BroadcastReceiver mReceiverDismissNotification =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Flags.sounddoseCustomization()
+ && DISMISS_CSD_NOTIFICATION.equals(intent.getAction())) {
+ if (D.BUG) Log.d(TAG, "Received DISMISS_CSD_NOTIFICATION");
+ destroy();
+ }
+ }
+ };
+
private @StringRes int getStringForWarning(@AudioManager.CsdWarning int csdWarning) {
switch (csdWarning) {
case AudioManager.CSD_WARNING_DOSE_REACHED_1X:
@@ -259,7 +319,7 @@
Log.w(TAG, "Notification dose repeat 5x is not shown for " + mCsdWarning);
return;
}
-
+ mCachedMediaStreamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mAudioManager.lowerVolumeToRs1();
sendNotification(/*for5XCsd=*/true);
}
@@ -288,7 +348,62 @@
.setAutoCancel(true)
.setCategory(Notification.CATEGORY_SYSTEM);
+ if (Flags.sounddoseCustomization()
+ && mActionIntents.isPresent()
+ && !mActionIntents.get().isEmpty()) {
+ ImmutableList<Pair<String, Intent>> actionIntentsList = mActionIntents.get();
+ for (Pair<String, Intent> intentPair : actionIntentsList) {
+ if (intentPair != null && intentPair.first != null && intentPair.second != null) {
+ PendingIntent pendingActionIntent =
+ PendingIntent.getBroadcast(mContext, 0, intentPair.second,
+ FLAG_IMMUTABLE);
+ builder.addAction(0, intentPair.first, pendingActionIntent);
+ // Register receiver to undo volume only when
+ // notification conaining the undo action would be sent.
+ if (intentPair.first == mContext.getString(R.string.volume_undo_action)) {
+ final IntentFilter filterUndo = new IntentFilter(
+ VolumeDialog.ACTION_VOLUME_UNDO);
+ mBroadcastDispatcher.registerReceiver(mReceiverUndo,
+ filterUndo,
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+
+ // Register receiver to learn if notification has been dismissed.
+ // This is required to unregister receivers to prevent leak.
+ Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+ .setPackage(mContext.getPackageName());
+ PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(mContext,
+ 0, dismissIntent, FLAG_IMMUTABLE);
+ mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
+ new IntentFilter(DISMISS_CSD_NOTIFICATION),
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+ builder.setDeleteIntent(pendingDismissIntent);
+ }
+ }
+ }
+ }
+
mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
builder.build());
}
+
+ /**
+ * Unregister the Undo action receiver when the notification is dismissed.
+ * Unregister cannot happen when CsdWarning dialog is dismissed
+ * as the notification lifecycle would be longer.
+ */
+ @VisibleForTesting
+ public void destroy() {
+ if (Flags.sounddoseCustomization()
+ && mActionIntents.isPresent()
+ && !mActionIntents.get().isEmpty()) {
+ mBroadcastDispatcher.unregisterReceiver(mReceiverUndo);
+ mBroadcastDispatcher.unregisterReceiver(mReceiverDismissNotification);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 795d427..6b02e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -50,6 +50,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -77,6 +78,7 @@
import android.provider.Settings.Global;
import android.text.InputFilter;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
@@ -140,11 +142,14 @@
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
+import com.google.common.collect.ImmutableList;
+
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.Consumer;
/**
@@ -315,6 +320,9 @@
private final com.android.systemui.util.time.SystemClock mSystemClock;
private final VolumePanelFlag mVolumePanelFlag;
private final VolumeDialogInteractor mInteractor;
+ // Optional actions for soundDose
+ private Optional<ImmutableList<Pair<String, Intent>>> mCsdWarningNotificationActions =
+ Optional.of(ImmutableList.of());
public VolumeDialogImpl(
Context context,
@@ -2198,6 +2206,11 @@
rescheduleTimeoutH();
}
+ public void setCsdWarningNotificationActionIntents(
+ ImmutableList<Pair<String, Intent>> actionIntent) {
+ mCsdWarningNotificationActions = Optional.of(actionIntent);
+ }
+
@VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
synchronized (mSafetyWarningLock) {
@@ -2212,7 +2225,8 @@
recheckH(null);
};
- mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
+ mCsdDialog = mCsdWarningDialogFactory.create(
+ csdWarning, cleanUp, mCsdWarningNotificationActions);
mCsdDialog.show();
}
recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3f1ec85..ec9b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,9 +20,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -67,7 +66,6 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -251,25 +249,7 @@
pip.showPictureInPictureMenu();
}
});
- pip.registerPipTransitionCallback(
- new PipTransitionController.PipTransitionCallback() {
- @Override
- public void onPipTransitionStarted(int direction, Rect pipBounds) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
- @Override
- public void onPipTransitionFinished(int direction) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
-
- @Override
- public void onPipTransitionCanceled(int direction) {
- // No op.
- }
- }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index b23dfdc..8595178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -21,6 +21,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.view.Display;
+import android.view.KeyboardShortcutGroup;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -55,6 +56,11 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
+ @Override
public Region getCurrentImeTouchRegion() {
return mWindowManager.getCurrentImeTouchRegion();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index c30bedd..12140b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -288,7 +288,7 @@
mockActivityQuery(true);
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mSpyContext).startActivity(intentCaptor.capture());
+ verify(mSpyContext).startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT));
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
mMenuViewLayer.getIntentForEditScreen().getAction());
}
@@ -299,6 +299,7 @@
mockActivityQuery(false);
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
verify(mSpyContext, never()).startActivity(any());
+ verify(mSpyContext, never()).startActivityAsUser(any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 8f7dc7cf..5ea5c21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -16,8 +16,9 @@
package com.android.systemui.accessibility.hearingaid;
+import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+
import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT;
-import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
import static com.google.common.truth.Truth.assertThat;
@@ -26,9 +27,13 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -42,6 +47,7 @@
import android.testing.TestableLooper;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.Spinner;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -86,14 +92,15 @@
public MockitoRule mockito = MockitoJUnit.rule();
private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF";
+ private static final String DEVICE_NAME = "test_name";
private static final String TEST_PKG = "pkg";
private static final String TEST_CLS = "cls";
private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS);
private static final String TEST_LABEL = "label";
+ private static final int TEST_PRESET_INDEX = 1;
+ private static final String TEST_PRESET_NAME = "test_preset";
@Mock
- private SystemUIDialog.Factory mSystemUIDialogFactory;
- @Mock
private SystemUIDialogManager mSystemUIDialogManager;
@Mock
private SysUiState mSysUiState;
@@ -118,6 +125,8 @@
@Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
+ private BluetoothDevice mDevice;
+ @Mock
private DeviceItem mHearingDeviceItem;
@Mock
private PackageManager mPackageManager;
@@ -125,7 +134,10 @@
private ActivityInfo mActivityInfo;
@Mock
private Drawable mDrawable;
+ @Mock
+ private HearingDevicesPresetsController mPresetsController;
private SystemUIDialog mDialog;
+ private SystemUIDialog.Factory mDialogFactory;
private HearingDevicesDialogDelegate mDialogDelegate;
private TestableLooper mTestableLooper;
private final List<CachedBluetoothDevice> mDevices = new ArrayList<>();
@@ -141,23 +153,23 @@
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice.isConnected()).thenReturn(true);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
+ when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+ when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
+
mContext.setMockPackageManager(mPackageManager);
-
- setUpPairNewDeviceDialog();
-
- when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
- .thenReturn(mDialog);
- }
-
- @Test
- public void createDialog_dialogShown() {
- assertThat(mDialogDelegate.createDialog()).isEqualTo(mDialog);
+ mDevices.add(mCachedDevice);
}
@Test
public void clickPairNewDeviceButton_intentActionMatch() {
+ setUpPairNewDeviceDialog();
mDialog.show();
getPairNewDeviceButton(mDialog).performClick();
@@ -185,6 +197,7 @@
@Test
public void onDeviceItemOnClicked_connectedDevice_disconnect() {
+ setUpDeviceListDialog();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
mDialogDelegate.onDeviceItemOnClicked(mHearingDeviceItem, new View(mContext));
@@ -231,50 +244,100 @@
assertThat(relatedToolsView.getChildCount()).isEqualTo(2);
}
+ @Test
+ public void showDialog_noPreset_presetGone() {
+ when(mPresetsController.getAllPresetInfo()).thenReturn(new ArrayList<>());
+ when(mPresetsController.getActivePresetIndex()).thenReturn(PRESET_INDEX_UNAVAILABLE);
+
+ setUpDeviceListDialog();
+ mDialog.show();
+
+ Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+ assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void showDialog_presetExist_presetSelected() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
+ when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+
+ setUpDeviceListDialog();
+ mDialog.show();
+
+ Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+ assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
+ }
+
+ @Test
+ public void onActiveDeviceChanged_presetExist_presetSelected() {
+ setUpDeviceListDialog();
+ mDialog.show();
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
+ when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+
+ mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
+ mTestableLooper.processAllMessages();
+
+ Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+ assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
+ }
+
+
+
private void setUpPairNewDeviceDialog() {
+ mDialogFactory = new SystemUIDialog.Factory(
+ mContext,
+ mSystemUIDialogManager,
+ mSysUiState,
+ getFakeBroadcastDispatcher(),
+ mDialogTransitionAnimator
+ );
mDialogDelegate = new HearingDevicesDialogDelegate(
mContext,
true,
- mSystemUIDialogFactory,
+ mDialogFactory,
mActivityStarter,
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
mAudioManager
);
- mDialog = new SystemUIDialog(
- mContext,
- 0,
- DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mSystemUIDialogManager,
- mSysUiState,
- getFakeBroadcastDispatcher(),
- mDialogTransitionAnimator,
- mDialogDelegate
- );
+
+ mDialog = mDialogDelegate.createDialog();
}
private void setUpDeviceListDialog() {
+ mDialogFactory = new SystemUIDialog.Factory(
+ mContext,
+ mSystemUIDialogManager,
+ mSysUiState,
+ getFakeBroadcastDispatcher(),
+ mDialogTransitionAnimator
+ );
mDialogDelegate = new HearingDevicesDialogDelegate(
mContext,
false,
- mSystemUIDialogFactory,
+ mDialogFactory,
mActivityStarter,
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
mAudioManager
);
- mDialog = new SystemUIDialog(
- mContext,
- 0,
- DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mSystemUIDialogManager,
- mSysUiState,
- getFakeBroadcastDispatcher(),
- mDialogTransitionAnimator,
- mDialogDelegate
- );
+
+ mDialog = mDialogDelegate.createDialog();
+ mDialogDelegate.setHearingDevicesPresetsController(mPresetsController);
+ }
+
+ private BluetoothHapPresetInfo getTestPresetInfo() {
+ BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
+ when(info.getName()).thenReturn(TEST_PRESET_NAME);
+ when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ return info;
}
private View getPairNewDeviceButton(SystemUIDialog dialog) {
@@ -285,6 +348,10 @@
return dialog.requireViewById(R.id.related_tools_container);
}
+ private View getPresetSpinner(SystemUIDialog dialog) {
+ return dialog.requireViewById(R.id.preset_spinner);
+ }
+
@After
public void reset() {
if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 7094848..e60848b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -120,11 +120,26 @@
private fun setUpDatabase(): List<FakeWidgetMetadata> {
return listOf(
- FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
- FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
- FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+ FakeWidgetMetadata(
+ widgetId = 11,
+ componentName = "com.android.fakePackage1/fakeWidget1",
+ rank = 3,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 12,
+ componentName = "com.android.fakePackage2/fakeWidget2",
+ rank = 2,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 13,
+ componentName = "com.android.fakePackage3/fakeWidget3",
+ rank = 1,
+ userSerialNumber = 10,
+ ),
)
- .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+ .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) }
}
private fun getBackupDataInputStream(): BackupDataInputStream {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index cde7a0e..983a435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -66,11 +66,28 @@
// Set up database
val expectedWidgets =
listOf(
- FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
- FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
- FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+ FakeWidgetMetadata(
+ widgetId = 11,
+ componentName = "com.android.fakePackage1/fakeWidget1",
+ rank = 3,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 12,
+ componentName = "com.android.fakePackage2/fakeWidget2",
+ rank = 2,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 13,
+ componentName = "com.android.fakePackage3/fakeWidget3",
+ rank = 1,
+ userSerialNumber = 10,
+ ),
)
- expectedWidgets.forEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+ expectedWidgets.forEach {
+ dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ }
// Get communal hub state
val state = underTest.getCommunalHubState()
@@ -128,7 +145,12 @@
assertThat(underTest.fileExists()).isFalse()
}
- data class FakeWidgetMetadata(val widgetId: Int, val componentName: String, val rank: Int)
+ data class FakeWidgetMetadata(
+ val widgetId: Int,
+ val componentName: String,
+ val rank: Int,
+ val userSerialNumber: Int,
+ )
companion object {
/**
@@ -140,7 +162,8 @@
{ actual, expected ->
actual?.widgetId == expected?.widgetId &&
actual?.componentName == expected?.componentName &&
- actual?.rank == expected?.rank
+ actual?.rank == expected?.rank &&
+ actual?.userSerialNumber == expected?.userSerialNumber
},
"represents",
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
new file mode 100644
index 0000000..eb0ab78
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.communal.data.db
+
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDatabaseMigrationsTest : SysuiTestCase() {
+
+ @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+ @get:Rule
+ val migrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ CommunalDatabase::class.java.canonicalName,
+ )
+
+ @Test
+ fun migrate1To2() {
+ // Create a communal database in version 1
+ val databaseV1 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 1)
+
+ // Populate some fake data
+ val fakeWidgetsV1 =
+ listOf(
+ FakeCommunalWidgetItemV1(1, "test_widget_1", 11),
+ FakeCommunalWidgetItemV1(2, "test_widget_2", 12),
+ FakeCommunalWidgetItemV1(3, "test_widget_3", 13),
+ )
+ databaseV1.insertWidgetsV1(fakeWidgetsV1)
+
+ // Verify fake widgets populated
+ databaseV1.verifyWidgetsV1(fakeWidgetsV1)
+
+ // Run migration and get database V2, the migration test helper verifies that the schema is
+ // updated correctly
+ val databaseV2 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 2,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_1_2,
+ )
+
+ // Verify data is migrated correctly
+ databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
+ }
+
+ private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+ widgets.forEach { widget ->
+ execSQL(
+ "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
+ "VALUES(${widget.widgetId}, '${widget.componentName}', ${widget.itemId})"
+ )
+ }
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV2(widgets: List<FakeCommunalWidgetItemV2>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ /**
+ * Returns the expected data after migration from V1 to V2, which is simply that the new user
+ * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
+ */
+ private fun FakeCommunalWidgetItemV1.getV2(): FakeCommunalWidgetItemV2 {
+ return FakeCommunalWidgetItemV2(
+ widgetId,
+ componentName,
+ itemId,
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED,
+ )
+ }
+
+ private data class FakeCommunalWidgetItemV1(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ )
+
+ private data class FakeCommunalWidgetItemV2(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ )
+
+ companion object {
+ private const val DATABASE_NAME = "communal_db"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index f77c7a6..d670508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,12 @@
@Test
fun addWidget_readValueInDb() =
testScope.runTest {
- val (widgetId, provider, priority) = widgetInfo1
+ val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
assertThat(entry).isEqualTo(communalWidgetItemEntry1)
@@ -80,11 +81,12 @@
@Test
fun deleteWidget_notInDb_returnsFalse() =
testScope.runTest {
- val (widgetId, provider, priority) = widgetInfo1
+ val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
}
@@ -95,11 +97,12 @@
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber
)
}
assertThat(widgets())
@@ -118,11 +121,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -144,11 +148,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -180,11 +185,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
existingWidgets.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -212,6 +218,7 @@
widgetId = widgetInfo3.widgetId,
provider = widgetInfo3.provider,
priority = 2,
+ userSerialNumber = widgetInfo3.userSerialNumber,
)
assertThat(widgets())
.containsExactly(
@@ -246,6 +253,7 @@
widgetId = fakeWidget.widgetId,
componentName = fakeWidget.componentName,
itemId = rank.uid,
+ userSerialNumber = fakeWidget.userSerialNumber,
)
expected[rank] = widget
}
@@ -258,13 +266,15 @@
widgetId = metadata.widgetId,
provider = metadata.provider,
priority = priority ?: metadata.priority,
+ userSerialNumber = metadata.userSerialNumber,
)
}
data class FakeWidgetMetadata(
val widgetId: Int,
val provider: ComponentName,
- val priority: Int
+ val priority: Int,
+ val userSerialNumber: Int,
)
companion object {
@@ -272,19 +282,22 @@
FakeWidgetMetadata(
widgetId = 1,
provider = ComponentName("pk_name", "cls_name_1"),
- priority = 1
+ priority = 1,
+ userSerialNumber = 0,
)
val widgetInfo2 =
FakeWidgetMetadata(
widgetId = 2,
provider = ComponentName("pk_name", "cls_name_2"),
- priority = 2
+ priority = 2,
+ userSerialNumber = 0,
)
val widgetInfo3 =
FakeWidgetMetadata(
widgetId = 3,
provider = ComponentName("pk_name", "cls_name_3"),
- priority = 3
+ priority = 3,
+ userSerialNumber = 10,
)
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
@@ -295,6 +308,7 @@
widgetId = widgetInfo1.widgetId,
componentName = widgetInfo1.provider.flattenToString(),
itemId = communalItemRankEntry1.uid,
+ userSerialNumber = widgetInfo1.userSerialNumber,
)
val communalWidgetItemEntry2 =
CommunalWidgetItem(
@@ -302,6 +316,7 @@
widgetId = widgetInfo2.widgetId,
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
+ userSerialNumber = widgetInfo2.userSerialNumber,
)
val communalWidgetItemEntry3 =
CommunalWidgetItem(
@@ -309,6 +324,7 @@
widgetId = widgetInfo3.widgetId,
componentName = widgetInfo3.provider.flattenToString(),
itemId = communalItemRankEntry3.uid,
+ userSerialNumber = widgetInfo3.userSerialNumber,
)
val fakeState =
CommunalHubState().apply {
@@ -318,11 +334,13 @@
widgetId = 1
componentName = "pk_name/fake_widget_1"
rank = 1
+ userSerialNumber = 0
},
CommunalHubState.CommunalWidgetItem().apply {
widgetId = 2
componentName = "pk_name/fake_widget_2"
rank = 2
+ userSerialNumber = 10
},
)
.toTypedArray()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index f34058b..20cb1e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -59,12 +59,14 @@
private val testHandler = FakeHandler(Looper.getMainLooper())
private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val defaultDisplay =
+ display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
private lateinit var displayRepository: DisplayRepositoryImpl
@Before
fun setup() {
- setDisplays(DEFAULT_DISPLAY)
+ setDisplays(listOf(defaultDisplay))
setAllDisplaysIncludingDisabled(DEFAULT_DISPLAY)
displayRepository =
DisplayRepositoryImpl(
@@ -434,19 +436,12 @@
fun defaultDisplayOff_changes() =
testScope.runTest {
val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
- setDisplays(
- listOf(
- display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_OFF)
- )
- )
+
+ whenever(defaultDisplay.state).thenReturn(Display.STATE_OFF)
displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
assertThat(defaultDisplayOff).isTrue()
- setDisplays(
- listOf(
- display(type = TYPE_INTERNAL, id = DEFAULT_DISPLAY, state = Display.STATE_ON)
- )
- )
+ whenever(defaultDisplay.state).thenReturn(Display.STATE_ON)
displayListener.value.onDisplayChanged(DEFAULT_DISPLAY)
assertThat(defaultDisplayOff).isFalse()
}
@@ -545,7 +540,10 @@
}
private fun setAllDisplaysIncludingDisabled(vararg ids: Int) {
- val displays = ids.map { display(type = TYPE_EXTERNAL, id = it) }.toTypedArray()
+ val displays =
+ (ids.toSet() - DEFAULT_DISPLAY) // Default display always added.
+ .map { display(type = TYPE_EXTERNAL, id = it) }
+ .toTypedArray() + defaultDisplay
whenever(
displayManager.getDisplays(
eq(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e2cca38..ae635b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -41,7 +41,6 @@
import android.os.Handler;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.IDreamManager;
import android.testing.TestableLooper;
import android.view.GestureDetector;
import android.view.IWindowManager;
@@ -106,7 +105,6 @@
@Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@Mock private AudioManager mAudioManager;
- @Mock private IDreamManager mDreamManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -165,7 +163,6 @@
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
mAudioManager,
- mDreamManager,
mDevicePolicyManager,
mLockPatternUtils,
mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
new file mode 100644
index 0000000..14837f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
+
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+
+ private val kosmos =
+ testKosmos().also {
+ it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ }
+
+ private val repo = kosmos.shortcutHelperCategoriesRepository
+ private val helper = kosmos.shortcutHelperTestHelper
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
+ @Test
+ fun categories_multipleSubscribers_replaysExistingValueToNewSubscribers() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ helper.showFromActivity()
+ val firstCategories by collectLastValue(repo.categories)
+
+ // Intentionally change shortcuts now. This simulates "current app" shortcuts changing
+ // when our helper is shown.
+ // We still want to return the shortcuts that were returned before our helper was
+ // showing.
+ fakeSystemSource.setGroups(emptyList())
+
+ val secondCategories by collectLastValue(repo.categories)
+ // Make sure the second subscriber receives the same value as the first subscriber, even
+ // though fetching shortcuts again would have returned a new result.
+ assertThat(secondCategories).isEqualTo(firstCategories)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 765cd01..c9c39b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -189,6 +189,15 @@
listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
)
+ private val standardPackageName1 = "standard.app.group1"
+
+ private val standardAppGroup1 =
+ KeyboardShortcutGroup(
+ "Standard app group 1",
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ )
+ .apply { packageName = standardPackageName1 }
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
@@ -220,7 +229,7 @@
val imeGroups = listOf(standardGroup1, standardGroup2, standardGroup3)
val imeCategory =
ShortcutCategory(
- type = ShortcutCategoryType.IME,
+ type = ShortcutCategoryType.InputMethodEditor,
subCategories =
listOf(
subCategoryForInputLanguageSwitchShortcuts,
@@ -230,17 +239,20 @@
)
)
+ val currentAppGroups = listOf(standardAppGroup1)
+ val currentAppPackageName = standardPackageName1
+
val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
val systemCategory =
ShortcutCategory(
- type = ShortcutCategoryType.SYSTEM,
+ type = ShortcutCategoryType.System,
subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1)
)
val multitaskingGroups = listOf(standardGroup2, standardGroup1)
val multitaskingCategory =
ShortcutCategory(
- type = ShortcutCategoryType.MULTI_TASKING,
+ type = ShortcutCategoryType.MultiTasking,
subCategories = listOf(standardSubCategory2, standardSubCategory1)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 4c1e869..57c8b44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -23,11 +23,12 @@
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.IME
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MULTI_TASKING
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.SYSTEM
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
@@ -48,14 +49,14 @@
private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
- private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource()
@OptIn(ExperimentalCoroutinesApi::class)
private val kosmos =
testKosmos().also {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
- it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val testScope = kosmos.testScope
@@ -117,7 +118,7 @@
TestShortcuts.systemCategory,
TestShortcuts.multitaskingCategory,
ShortcutCategory(
- type = IME,
+ type = InputMethodEditor,
subCategories =
TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels
),
@@ -137,7 +138,7 @@
assertThat(categories)
.containsExactly(
ShortcutCategory(
- type = SYSTEM,
+ type = System,
subCategories =
TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
),
@@ -160,7 +161,7 @@
.containsExactly(
TestShortcuts.systemCategory,
ShortcutCategory(
- type = MULTI_TASKING,
+ type = MultiTasking,
subCategories =
TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
),
@@ -182,7 +183,7 @@
TestShortcuts.systemCategory,
TestShortcuts.multitaskingCategory,
ShortcutCategory(
- type = IME,
+ type = InputMethodEditor,
subCategories =
TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved
),
@@ -201,7 +202,7 @@
assertThat(categories)
.containsExactly(
ShortcutCategory(
- type = SYSTEM,
+ type = System,
subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
),
TestShortcuts.multitaskingCategory,
@@ -222,7 +223,7 @@
.containsExactly(
TestShortcuts.systemCategory,
ShortcutCategory(
- type = MULTI_TASKING,
+ type = MultiTasking,
subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
),
TestShortcuts.imeCategory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
index 0757ea1..f8e2f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
@@ -20,8 +20,15 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity
import com.android.systemui.keyboard.shortcut.shortcutHelperActivityStarter
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity
import com.android.systemui.kosmos.Kosmos
@@ -32,6 +39,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,10 +48,18 @@
@RunWith(AndroidJUnit4::class)
class ShortcutHelperActivityStarterTest : SysuiTestCase() {
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+
private val kosmos =
Kosmos().also {
it.testCase = this
it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val testScope = kosmos.testScope
@@ -51,6 +67,12 @@
private val fakeStartActivity = kosmos.fakeShortcutHelperStartActivity
private val starter = kosmos.shortcutHelperActivityStarter
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
@Test
fun start_doesNotStartByDefault() =
testScope.runTest {
@@ -70,13 +92,30 @@
}
@Test
+ fun start_onToggle_noShortcuts_doesNotStartActivity() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(emptyList())
+ fakeMultiTaskingSource.setGroups(emptyList())
+
+ starter.start()
+
+ testHelper.toggle(deviceId = 456)
+
+ assertThat(fakeStartActivity.startIntents).isEmpty()
+ }
+
+ @Test
fun start_onToggle_multipleTimesStartsActivityOnlyWhenNotStarted() =
testScope.runTest {
starter.start()
+ // Starts
testHelper.toggle(deviceId = 456)
+ // Stops
testHelper.toggle(deviceId = 456)
+ // Starts again
testHelper.toggle(deviceId = 456)
+ // Stops
testHelper.toggle(deviceId = 456)
verifyShortcutHelperActivityStarted(numTimes = 2)
@@ -93,6 +132,18 @@
}
@Test
+ fun start_onRequestShowShortcuts_noShortcuts_doesNotStartActivity() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(emptyList())
+ fakeMultiTaskingSource.setGroups(emptyList())
+ starter.start()
+
+ testHelper.showFromActivity()
+
+ assertThat(fakeStartActivity.startIntents).isEmpty()
+ }
+
+ @Test
fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyOnce() =
testScope.runTest {
starter.start()
@@ -109,13 +160,21 @@
testScope.runTest {
starter.start()
+ // No-op. Already hidden.
testHelper.hideFromActivity()
+ // No-op. Already hidden.
testHelper.hideForSystem()
+ // Show 1st time.
testHelper.toggle(deviceId = 987)
+ // No-op. Already shown.
testHelper.showFromActivity()
+ // Hidden.
testHelper.hideFromActivity()
+ // No-op. Already hidden.
testHelper.hideForSystem()
+ // Show 2nd time.
testHelper.toggle(deviceId = 456)
+ // No-op. Already shown.
testHelper.showFromActivity()
verifyShortcutHelperActivityStarted(numTimes = 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 80d487c..69fc463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -21,6 +21,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
@@ -34,6 +42,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,10 +51,19 @@
@RunWith(AndroidJUnit4::class)
class ShortcutHelperViewModelTest : SysuiTestCase() {
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
+
private val kosmos =
Kosmos().also {
it.testCase = this
it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
}
private val testScope = kosmos.testScope
@@ -53,6 +71,12 @@
private val sysUiState = kosmos.sysUiState
private val viewModel = kosmos.shortcutHelperViewModel
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
@Test
fun shouldShow_falseByDefault() =
testScope.runTest {
@@ -194,4 +218,17 @@
assertThat(activeUiState.defaultSelectedCategory)
.isEqualTo(activeUiState.shortcutCategories.first().type)
}
+
+ @Test
+ fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ testScope.runTest {
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory)
+ .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 03afcb7..e68a4a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -77,6 +77,7 @@
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
@@ -101,6 +102,7 @@
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
@@ -188,6 +190,7 @@
private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
private @Mock ScrimController mScrimController;
private @Mock IActivityTaskManager mActivityTaskManagerService;
+ private @Mock IStatusBarService mStatusBarService;
private @Mock SysuiColorExtractor mColorExtractor;
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -211,6 +214,7 @@
private @Mock SystemSettings mSystemSettings;
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
+ private @Mock ProcessWrapper mProcessWrapper;
private FakeSystemClock mSystemClock;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@@ -247,6 +251,7 @@
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -1225,10 +1230,12 @@
() -> mActivityTransitionAnimator,
() -> mScrimController,
mActivityTaskManagerService,
+ mStatusBarService,
mFeatureFlags,
mSecureSettings,
mSystemSettings,
mSystemClock,
+ mProcessWrapper,
mDispatcher,
() -> mDreamViewModel,
() -> mCommunalTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 144e634..23fd997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -18,7 +18,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setSceneTransition
-import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.utils.GlobalWindowManager
@@ -64,7 +63,6 @@
globalWindowManager = globalWindowManager,
applicationScope = testScope.backgroundScope,
bgDispatcher = kosmos.testDispatcher,
- sceneInteractor = kosmos.sceneInteractor,
)
resourceTrimmer.start()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index f0ad510..59f16d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -84,7 +83,7 @@
fun testAppliesDefaultBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
configurationRepository.onConfigurationChange()
runCurrent()
@@ -98,7 +97,7 @@
fun testAppliesSplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
configurationRepository.onConfigurationChange()
runCurrent()
@@ -112,7 +111,7 @@
fun testDoesNotApplySplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
- kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
clockRepository.setCurrentClock(clockController)
configurationRepository.onConfigurationChange()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 974e3bb..8bc0a60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -31,7 +31,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setSceneTransition
-import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -75,7 +74,6 @@
transitionInteractor = kosmos.keyguardTransitionInteractor,
dismissInteractor = dismissInteractorWithDependencies.interactor,
applicationScope = testScope.backgroundScope,
- sceneInteractor = kosmos.sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 824132a..3b0f623 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1314,6 +1314,7 @@
}
@Test
+ @DisableSceneContainer
fun occludedToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to OCCLUDED
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index 22181f8..7e249e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -24,10 +24,12 @@
import android.content.mockedContext
import android.os.PowerManager
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -78,6 +80,7 @@
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testCanWakeDirectlyToGone_keyguardServiceEnabledThenDisabled() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -114,6 +117,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -178,6 +182,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testCanWakeDirectlyToGone_wakeAndUnlock() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -201,6 +206,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testCanWakeDirectlyToGone_andSetsAlarm_ifPowerButtonDoesNotLockImmediately() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -224,6 +230,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testSetsCanIgnoreAuth_andSetsAlarm_whenTimingOut() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
@@ -267,6 +274,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testCancelsFirstAlarm_onWake_withSecondAlarmSet() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 4a39a9b..6e381ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
@@ -48,6 +47,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.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -57,6 +57,7 @@
import org.mockito.ArgumentMatchers.anyString
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class ClockSectionTest : SysuiTestCase() {
@@ -127,7 +128,7 @@
fun testApplyDefaultConstraints_LargeClock_SplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
advanceUntilIdle()
}
@@ -143,11 +144,11 @@
fun testApplyDefaultConstraints_LargeClock_NonSplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
- val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+ val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
val isLargeClockVisible by
collectLastValue(keyguardClockViewModel.isLargeClockVisible)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -168,11 +169,11 @@
kosmos.testScope.runTest {
with(kosmos) {
DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
- val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+ val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
val isLargeClockVisible by
collectLastValue(keyguardClockViewModel.isLargeClockVisible)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -193,11 +194,11 @@
kosmos.testScope.runTest {
with(kosmos) {
DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
- val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+ val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
val isLargeClockVisible by
collectLastValue(keyguardClockViewModel.isLargeClockVisible)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -217,11 +218,11 @@
fun testApplyDefaultConstraints_SmallClock_SplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
- val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+ val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
val isLargeClockVisible by
collectLastValue(keyguardClockViewModel.isLargeClockVisible)
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardClockInteractor.setClockSize(ClockSize.SMALL)
fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -241,11 +242,11 @@
fun testApplyDefaultConstraints_SmallClock_NonSplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
- val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
+ val isShadeLayoutWide by collectLastValue(shadeRepository.isShadeLayoutWide)
val isLargeClockVisible by
collectLastValue(keyguardClockViewModel.isLargeClockVisible)
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.SMALL)
fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -334,7 +335,7 @@
}
companion object {
- private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
- private val ENHANCED_SMART_SPACE_HEIGHT = 11
+ private const val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
+ private const val ENHANCED_SMART_SPACE_HEIGHT = 11
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 40663ce..17e1b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -38,7 +38,6 @@
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
@@ -88,7 +87,7 @@
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardRepository.setClockShouldBeCentered(true)
keyguardClockRepository.setClockSize(ClockSize.LARGE)
}
@@ -103,7 +102,7 @@
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardRepository.setClockShouldBeCentered(false)
keyguardClockRepository.setClockSize(ClockSize.LARGE)
}
@@ -118,7 +117,7 @@
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
keyguardRepository.setClockShouldBeCentered(false)
keyguardClockRepository.setClockSize(ClockSize.SMALL)
}
@@ -133,7 +132,7 @@
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
keyguardClockRepository.setClockSize(ClockSize.SMALL)
}
@@ -146,7 +145,7 @@
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
keyguardClockRepository.setClockSize(ClockSize.LARGE)
}
@@ -234,7 +233,7 @@
fun testSmallClockTop_splitShade_composeLockscreenOn() =
testScope.runTest {
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
}
@@ -249,7 +248,7 @@
fun testSmallClockTop_splitShade_composeLockscreenOff() =
testScope.runTest {
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Split)
+ shadeRepository.setShadeLayoutWide(true)
fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
}
@@ -262,7 +261,7 @@
fun testSmallClockTop_nonSplitShade_composeLockscreenOn() =
testScope.runTest {
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
}
@@ -275,7 +274,7 @@
fun testSmallClockTop_nonSplitShade_composeLockscreenOff() =
testScope.runTest {
with(kosmos) {
- shadeRepository.setShadeMode(ShadeMode.Single)
+ shadeRepository.setShadeLayoutWide(false)
fakeSystemBarUtilsProxy.fakeKeyguardStatusBarHeight = KEYGUARD_STATUS_BAR_HEIGHT
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index fbeb6d8..732bef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -44,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -68,6 +69,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private ProcessWrapper mProcessWrapper;
@Captor
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -86,13 +89,15 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mSessionTracker = new SessionTracker(
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
mKeyguardStateController,
- mUiEventLogger
+ mUiEventLogger,
+ mProcessWrapper
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index c0d411b..785d5a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -16,19 +16,26 @@
package com.android.systemui.mediaprojection.data.repository
+import android.hardware.display.displayManager
import android.media.projection.MediaProjectionInfo
import android.os.Binder
+import android.os.Handler
import android.os.UserHandle
import android.view.ContentRecordingSession
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
@@ -36,7 +43,9 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -47,6 +56,7 @@
private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val displayManager = kosmos.displayManager
private val repo = kosmos.realMediaProjectionRepository
@@ -139,6 +149,37 @@
}
@Test
+ fun mediaProjectionState_entireScreen_validVirtualDisplayId_hasHostDeviceName() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+
+ val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ session.virtualDisplayId = 45
+ val displayInfo = mock<Display>().apply { whenever(this.name).thenReturn("Test Name") }
+ whenever(displayManager.getDisplay(45)).thenReturn(displayInfo)
+
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+ assertThat((state as MediaProjectionState.Projecting.EntireScreen).hostDeviceName)
+ .isEqualTo("Test Name")
+ }
+
+ @Test
+ fun mediaProjectionState_entireScreen_invalidVirtualDisplayId_nullHostDeviceName() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+
+ val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ session.virtualDisplayId = 45
+ whenever(displayManager.getDisplay(45)).thenReturn(null)
+
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+ assertThat((state as MediaProjectionState.Projecting.EntireScreen).hostDeviceName)
+ .isNull()
+ }
+
+ @Test
fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
testScope.runTest {
val token = createToken()
@@ -179,6 +220,90 @@
}
@Test
+ fun mediaProjectionState_singleTask_validVirtualDisplayId_hasHostDeviceName() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+ fakeActivityTaskManager.addRunningTasks(task)
+
+ val session = ContentRecordingSession.createTaskSession(token.asBinder())
+ session.virtualDisplayId = 45
+ val displayInfo = mock<Display>().apply { whenever(this.name).thenReturn("Test Name") }
+ whenever(displayManager.getDisplay(45)).thenReturn(displayInfo)
+
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+ assertThat((state as MediaProjectionState.Projecting.SingleTask).hostDeviceName)
+ .isEqualTo("Test Name")
+ }
+
+ @Test
+ fun mediaProjectionState_singleTask_invalidVirtualDisplayId_nullHostDeviceName() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+ fakeActivityTaskManager.addRunningTasks(task)
+
+ val session = ContentRecordingSession.createTaskSession(token.asBinder())
+ session.virtualDisplayId = 45
+ whenever(displayManager.getDisplay(45)).thenReturn(null)
+
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = session)
+
+ assertThat((state as MediaProjectionState.Projecting.SingleTask).hostDeviceName)
+ .isNull()
+ }
+
+ /** Regression test for b/352483752. */
+ @Test
+ fun mediaProjectionState_sessionStartedThenImmediatelyStopped_emitsOnlyNotProjecting() =
+ testScope.runTest {
+ val fakeTasksRepo = FakeTasksRepository()
+ val repoWithTimingControl =
+ MediaProjectionManagerRepository(
+ // fakeTasksRepo lets us have control over when the background dispatcher
+ // finishes fetching the tasks info.
+ tasksRepository = fakeTasksRepo,
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ displayManager = displayManager,
+ handler = Handler.getMain(),
+ applicationScope = kosmos.applicationCoroutineScope,
+ backgroundDispatcher = kosmos.testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ logger = logcatLogBuffer("TestMediaProjection"),
+ )
+
+ val state by collectLastValue(repoWithTimingControl.mediaProjectionState)
+
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+
+ // Dispatch a session using a task session so that MediaProjectionManagerRepository
+ // has to ask TasksRepository for the tasks info.
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(token.asBinder())
+ )
+ // FakeTasksRepository is set up to not return the tasks info until the test manually
+ // calls [FakeTasksRepository#setRunningTaskResult]. At this point,
+ // MediaProjectionManagerRepository is waiting for the tasks info and hasn't emitted
+ // anything yet.
+
+ // Before the tasks info comes back, dispatch a stop event.
+ fakeMediaProjectionManager.dispatchOnStop()
+
+ // Then let the tasks info come back.
+ fakeTasksRepo.setRunningTaskResult(task)
+
+ // Verify that MediaProjectionManagerRepository threw away the tasks info because
+ // a newer callback event (#onStop) occurred.
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
fun stopProjecting_invokesManager() =
testScope.runTest {
repo.stopProjecting()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..ce2b983
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager
+import android.os.IBinder
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Fake tasks repository that gives us fine-grained control over when the result of
+ * [findRunningTaskFromWindowContainerToken] gets emitted.
+ */
+class FakeTasksRepository : TasksRepository {
+ override suspend fun launchRecentTask(taskInfo: ActivityManager.RunningTaskInfo) {}
+
+ private val findRunningTaskResult: CompletableDeferred<ActivityManager.RunningTaskInfo?> =
+ CompletableDeferred()
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): ActivityManager.RunningTaskInfo? {
+ return findRunningTaskResult.await()
+ }
+
+ fun setRunningTaskResult(task: ActivityManager.RunningTaskInfo?) {
+ findRunningTaskResult.complete(task)
+ }
+
+ override val foregroundTask: Flow<ActivityManager.RunningTaskInfo> = emptyFlow()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index b169cc1..b4db6da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -27,6 +27,7 @@
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
@@ -63,7 +64,7 @@
private var triggerThreshold: Float = 0.0f
private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
@Mock private lateinit var vibratorHelper: VibratorHelper
- @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
private val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
@@ -78,7 +79,7 @@
mBackPanelController =
BackPanelController(
context,
- windowManager,
+ viewCaptureAwareWindowManager,
ViewConfiguration.get(context),
Handler.createAsync(testableLooper.looper),
systemClock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 98ff6c9..45d77f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -29,6 +29,8 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +40,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -70,6 +73,7 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -162,6 +166,8 @@
@Mock
ButtonDispatcher mImeSwitchButton;
@Mock
+ KeyButtonView mImeSwitchButtonView;
+ @Mock
ButtonDispatcher mBackButton;
@Mock
NavigationBarTransitions mNavigationBarTransitions;
@@ -433,6 +439,45 @@
}
@Test
+ public void testImeSwitcherClick() {
+ mNavigationBar.init();
+ mNavigationBar.onViewAttached();
+ mNavigationBar.onImeSwitcherClick(mImeSwitchButtonView);
+
+ verify(mUiEventLogger).log(NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mInputMethodManager)
+ .onImeSwitchButtonClickFromSystem(mNavigationBar.mDisplayId);
+ verify(mInputMethodManager, never()).showInputMethodPickerFromSystem(
+ anyBoolean() /* showAuxiliarySubtypes */, anyInt() /* displayId */);
+ } else {
+ verify(mInputMethodManager, never())
+ .onImeSwitchButtonClickFromSystem(anyInt() /* displayId */);
+ verify(mInputMethodManager).showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mNavigationBar.mDisplayId);
+ }
+ }
+
+ @Test
+ public void testImeSwitcherLongClick() {
+ mNavigationBar.init();
+ mNavigationBar.onViewAttached();
+ mNavigationBar.onImeSwitcherLongClick(mImeSwitchButtonView);
+
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_TAP);
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mUiEventLogger).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mInputMethodManager).showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mNavigationBar.mDisplayId);
+ } else {
+ verify(mUiEventLogger, never()).log(NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mInputMethodManager, never()).showInputMethodPickerFromSystem(
+ anyBoolean() /* showAuxiliarySubtypes */, anyInt() /* displayId */);
+ }
+ }
+
+ @Test
public void testRegisteredWithUserTracker() {
mNavigationBar.init();
mNavigationBar.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
index b0265c0..3621ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
@@ -103,7 +103,7 @@
public void setIsLightsOut_AutoDim() {
mTransitions.setAutoDim(true);
- assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT));
+ assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_OPAQUE));
assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 16a022f..8681123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -276,8 +276,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLauncherApps = mock(LauncherApps.class);
- mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
- mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
+ mManager = new PeopleSpaceWidgetManager(mContext, Optional.of(mAppWidgetManager),
+ mIPeopleManager, mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
mNotificationManager, mFakeExecutor, mUserTracker);
mManager.attach(mListenerService);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 415cc7c..988769f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -471,7 +471,7 @@
}
@Test
- fun onPrepareForLaunch_paddingForLaunchAnimationIsConfigured() {
+ fun getPaddingForLaunchAnimation_onLongClickedState_paddingForLaunchAnimationIsConfigured() {
val startingWidth = 100
val startingHeight = 50
val deltaWidth = (QSTileViewImpl.LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * startingWidth
@@ -480,8 +480,8 @@
// GIVEN that long-press effect properties are initialized
tileView.initializeLongPressProperties(startingHeight, startingWidth)
- // WHEN the tile is preparing for the launch animation
- tileView.prepareForLaunch()
+ // WHEN the long-press effect has ended in the long-click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.LONG_CLICKED)
// THE animation padding corresponds to the tile's growth due to the effect
val padding = tileView.getPaddingForLaunchAnimation()
@@ -497,6 +497,22 @@
}
@Test
+ fun getPaddingForLaunchAnimation_notInLongClickState_paddingForLaunchAnimationIsEmpty() {
+ val startingWidth = 100
+ val startingHeight = 50
+
+ // GIVEN that long-press effect properties are initialized
+ tileView.initializeLongPressProperties(startingHeight, startingWidth)
+
+ // WHEN the long-press effect has ended in the click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.CLICKED)
+
+ // THE animation padding is empty
+ val padding = tileView.getPaddingForLaunchAnimation()
+ assertThat(padding.isEmpty).isTrue()
+ }
+
+ @Test
fun onActivityLaunchAnimationEnd_onFreshTile_longPressPropertiesAreReset() {
// WHEN an activity launch animation ends on a fresh tile
tileView.onActivityLaunchAnimationEnd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 798e9fb..d6bde27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.service.quicksettings.Tile;
import android.testing.TestableLooper;
@@ -32,6 +33,7 @@
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
+import com.android.server.display.feature.flags.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -131,6 +133,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
public void testActive_clicked_featureIsActivated() {
when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false);
mTile.refreshState();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 5273495..eea02ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -61,6 +61,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.wifi.WifiUtils;
@@ -160,7 +161,7 @@
@Mock
InternetDialogController.InternetDialogCallback mInternetDialogCallback;
@Mock
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
@Mock
private ToastFactory mToastFactory;
@Mock
@@ -232,8 +233,9 @@
mSubscriptionManager, mTelephonyManager, mWifiManager,
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
- mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController, mDialogTransitionAnimator, mWifiStateWorker, mFlags);
+ mWindowManager, mToastFactory, mWorkerHandler,
+ mCarrierConfigTracker, mLocationController, mDialogTransitionAnimator,
+ mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 2444af7..477c50b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -18,6 +18,8 @@
import static android.os.Process.myUid;
+import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -69,10 +71,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-/**
- * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot
- * Notification.
- */
public class RecordingControllerTest extends SysuiTestCase {
private static final int TEST_USER_ID = 12345;
@@ -146,6 +144,7 @@
mFeatureFlags,
() -> mDevicePolicyResolver,
mUserTracker,
+ new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")),
mMediaProjectionMetricsLogger,
mScreenCaptureDisabledDialogDelegate,
mScreenRecordDialogFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 0b81b5e..8d3a29a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -69,7 +69,7 @@
@Before
fun setUp() {
- whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+ whenever(controllerFactory.create(any())).thenReturn(controller)
whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
}
@@ -83,8 +83,8 @@
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
- verify(controllerFactory).create(eq(internalDisplay), any())
- verify(controllerFactory, never()).create(eq(externalDisplay), any())
+ verify(controllerFactory).create(eq(internalDisplay))
+ verify(controllerFactory, never()).create(eq(externalDisplay))
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -118,8 +118,8 @@
callback
)
- verify(controllerFactory).create(eq(internalDisplay), any())
- verify(controllerFactory, never()).create(eq(externalDisplay), any())
+ verify(controllerFactory).create(eq(internalDisplay))
+ verify(controllerFactory, never()).create(eq(externalDisplay))
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -151,7 +151,7 @@
@Test
fun executeScreenshots_allowedTypes_allCaptured() =
testScope.runTest {
- whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+ whenever(controllerFactory.create(any())).thenReturn(controller)
setDisplays(
display(TYPE_INTERNAL, id = 0),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index baf1357..193d29c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -334,13 +334,17 @@
}
private void resetPackageManagerMockingForUsingFallbackBacklinks() {
+ ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
reset(mPackageManager);
when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
- // First the logic queries whether a package has a launcher activity, this should
+ // Firstly, the logic queries whether a package has a launcher activity, this should
// resolve otherwise the logic filters out the task.
- .thenReturn(createBacklinksTaskResolveInfo())
- // Then logic queries with the backlinks intent, this should not resolve for the
+ .thenReturn(backlinksTaskResolveInfo)
+ // Secondly, the logic builds a fallback main launcher intent, this should also
+ // resolve for the fallback intent to build correctly.
+ .thenReturn(backlinksTaskResolveInfo)
+ // Lastly, logic queries with the backlinks intent, this should not resolve for the
// logic to use the fallback intent.
.thenReturn(null);
}
@@ -360,6 +364,8 @@
assertThat(actualBacklinksIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
assertThat(actualBacklinksIntent.getAction()).isEqualTo(ACTION_MAIN);
assertThat(actualBacklinksIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+ assertThat(actualBacklinksIntent.getComponent()).isEqualTo(
+ new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, BACKLINKS_TASK_APP_NAME));
}
private static ResolveInfo createBacklinksTaskResolveInfo() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 97acc6e..7d4918a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -19,8 +19,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -28,7 +28,6 @@
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeRepositoryImplTest : SysuiTestCase() {
@@ -91,37 +90,37 @@
@Test
fun updateLegacyShadeTracking() =
testScope.runTest {
- assertThat(underTest.legacyShadeTracking.value).isEqualTo(false)
+ assertThat(underTest.legacyShadeTracking.value).isFalse()
underTest.setLegacyShadeTracking(true)
- assertThat(underTest.legacyShadeTracking.value).isEqualTo(true)
+ assertThat(underTest.legacyShadeTracking.value).isTrue()
}
@Test
fun updateLegacyLockscreenShadeTracking() =
testScope.runTest {
- assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(false)
+ assertThat(underTest.legacyLockscreenShadeTracking.value).isFalse()
underTest.setLegacyLockscreenShadeTracking(true)
- assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(true)
+ assertThat(underTest.legacyLockscreenShadeTracking.value).isTrue()
}
@Test
fun updateLegacyQsTracking() =
testScope.runTest {
- assertThat(underTest.legacyQsTracking.value).isEqualTo(false)
+ assertThat(underTest.legacyQsTracking.value).isFalse()
underTest.setLegacyQsTracking(true)
- assertThat(underTest.legacyQsTracking.value).isEqualTo(true)
+ assertThat(underTest.legacyQsTracking.value).isTrue()
}
@Test
fun updateLegacyExpandedOrAwaitingInputTransfer() =
testScope.runTest {
- assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isEqualTo(false)
+ assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isFalse()
underTest.setLegacyExpandedOrAwaitingInputTransfer(true)
- assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isEqualTo(true)
+ assertThat(underTest.legacyExpandedOrAwaitingInputTransfer.value).isTrue()
}
@Test
@@ -142,36 +141,46 @@
@Test
fun updateLegacyIsQsExpanded() =
testScope.runTest {
- assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(false)
+ assertThat(underTest.legacyIsQsExpanded.value).isFalse()
underTest.setLegacyIsQsExpanded(true)
- assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true)
+ assertThat(underTest.legacyIsQsExpanded.value).isTrue()
}
@Test
fun updateLegacyExpandImmediate() =
testScope.runTest {
- assertThat(underTest.legacyExpandImmediate.value).isEqualTo(false)
+ assertThat(underTest.legacyExpandImmediate.value).isFalse()
underTest.setLegacyExpandImmediate(true)
- assertThat(underTest.legacyExpandImmediate.value).isEqualTo(true)
+ assertThat(underTest.legacyExpandImmediate.value).isTrue()
}
@Test
fun updateLegacyQsFullscreen() =
testScope.runTest {
- assertThat(underTest.legacyQsFullscreen.value).isEqualTo(false)
+ assertThat(underTest.legacyQsFullscreen.value).isFalse()
underTest.setLegacyQsFullscreen(true)
- assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true)
+ assertThat(underTest.legacyQsFullscreen.value).isTrue()
}
@Test
fun updateLegacyIsClosing() =
testScope.runTest {
- assertThat(underTest.legacyIsClosing.value).isEqualTo(false)
+ assertThat(underTest.legacyIsClosing.value).isFalse()
underTest.setLegacyIsClosing(true)
- assertThat(underTest.legacyIsClosing.value).isEqualTo(true)
+ assertThat(underTest.legacyIsClosing.value).isTrue()
+ }
+
+ @Test
+ fun isShadeLayoutWide() =
+ testScope.runTest {
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ assertThat(isShadeLayoutWide).isFalse()
+
+ underTest.setShadeLayoutWide(true)
+ assertThat(isShadeLayoutWide).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 2f52248..150f53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.PluginLifecycleManager
@@ -74,6 +75,7 @@
private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
+ private lateinit var pickerConfig: ClockPickerConfig
private val featureFlags = FakeFeatureFlags()
companion object {
@@ -82,9 +84,9 @@
return null!!
}
- private fun failThumbnail(clockId: ClockId): Drawable? {
- fail("Unexpected call to getThumbnail: $clockId")
- return null
+ private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
+ fail("Unexpected call to getClockPickerConfig: $clockId")
+ return null!!
}
}
@@ -123,22 +125,31 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
- private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
+ private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
override fun getClocks() = metadata
- override fun createClock(settings: ClockSettings): ClockController =
- createCallbacks[settings.clockId!!]!!(settings.clockId!!)
- override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+
+ override fun createClock(settings: ClockSettings): ClockController {
+ val clockId = settings.clockId ?: throw IllegalArgumentException("No clockId specified")
+ return createCallbacks[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No callback for '$clockId'")
+ }
+
+ override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
+ return pickerConfigs[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No picker config for '$clockId'")
+ }
+
override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
create: (ClockId) -> ClockController = ::failFactory,
- getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
+ getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
): FakeClockPlugin {
metadata.add(ClockMetadata(id))
createCallbacks[id] = create
- thumbnailCallbacks[id] = getThumbnail
+ pickerConfigs[id] = getPickerConfig
return this
}
}
@@ -148,9 +159,10 @@
scheduler = TestCoroutineScheduler()
dispatcher = StandardTestDispatcher(scheduler)
scope = TestScope(dispatcher)
+ pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
fakeDefaultProvider = FakeClockPlugin()
- .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { mockThumbnail })
+ .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
@@ -215,8 +227,8 @@
@Test
fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
val plugin1 = FakeClockPlugin()
- .addClock("clock_1", { mockClock }, { mockThumbnail })
- .addClock("clock_2", { mockClock }, { mockThumbnail })
+ .addClock("clock_1", { mockClock }, { pickerConfig })
+ .addClock("clock_2", { mockClock }, { pickerConfig })
val lifecycle1 = spy(FakeLifecycle("1", plugin1))
val plugin2 = FakeClockPlugin()
@@ -238,8 +250,8 @@
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
- assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
+ assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2522ed7..bbe03f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -72,6 +72,10 @@
.thenReturn(mockSmallClockView)
whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
.thenReturn(mockLargeClockView)
+ whenever(resources.getString(R.string.clock_default_name))
+ .thenReturn("DEFAULT_CLOCK_NAME")
+ whenever(resources.getString(R.string.clock_default_description))
+ .thenReturn("DEFAULT_CLOCK_DESC")
whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
.thenReturn(mockClockThumbnail)
whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
@@ -85,7 +89,7 @@
// All providers need to provide clocks & thumbnails for exposed clocks
for (metadata in provider.getClocks()) {
assertNotNull(provider.createClock(metadata.clockId))
- assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ assertNotNull(provider.getClockPickerConfig(metadata.clockId))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 6985a27..63e56ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -66,7 +66,9 @@
@Before
public void setUp() {
- mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager);
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
+ mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager, -1);
mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog;
mKeyboardShortcutListSearch.mContext = mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index 6ad8b8b..105cf16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -54,6 +54,7 @@
import org.mockito.stubbing.Answer;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -71,6 +72,8 @@
@Before
public void setUp() {
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
mKeyboardShortcuts = new KeyboardShortcuts(mContext, mWindowManager);
KeyboardShortcuts.sInstance = mKeyboardShortcuts;
mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index c8397bd..5005d16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -75,7 +75,9 @@
@Test
fun message_entireScreen_unknownDevice() {
- createAndSetDelegate(ENTIRE_SCREEN, deviceName = null)
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE, hostDeviceName = null)
+ )
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -87,7 +89,12 @@
@Test
fun message_entireScreen_hasDevice() {
- createAndSetDelegate(ENTIRE_SCREEN, deviceName = "My Favorite Device")
+ createAndSetDelegate(
+ MediaProjectionState.Projecting.EntireScreen(
+ HOST_PACKAGE,
+ hostDeviceName = "My Favorite Device"
+ )
+ )
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -110,9 +117,9 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent)
),
- deviceName = null,
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -133,9 +140,9 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
+ hostDeviceName = "My Favorite Device",
createTask(taskId = 1, baseIntent = baseIntent)
),
- deviceName = "My Favorite Device",
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -161,9 +168,9 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent)
),
- deviceName = null,
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -189,9 +196,9 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
- createTask(taskId = 1, baseIntent = baseIntent)
+ hostDeviceName = "My Favorite Device",
+ createTask(taskId = 1, baseIntent = baseIntent),
),
- deviceName = "My Favorite Device",
)
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -240,10 +247,7 @@
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
- private fun createAndSetDelegate(
- state: MediaProjectionState.Projecting,
- deviceName: String? = null,
- ) {
+ private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
underTest =
EndCastScreenToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
@@ -252,15 +256,19 @@
ProjectionChipModel.Projecting(
ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE,
state,
- deviceName,
),
)
}
companion object {
private const val HOST_PACKAGE = "fake.host.package"
- private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+ private val ENTIRE_SCREEN =
+ MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE, hostDeviceName = null)
private val SINGLE_TASK =
- MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1)
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index fe29140..c9a7c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -19,7 +19,6 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -38,7 +37,6 @@
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
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.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.policy.CastDevice
@@ -47,9 +45,7 @@
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.mockito.ArgumentMatchers
import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -65,17 +61,6 @@
private val mockScreenCastDialog = mock<SystemUIDialog>()
private val mockGenericCastDialog = mock<SystemUIDialog>()
- private val chipBackgroundView = mock<ChipBackgroundContainer>()
- private val chipView =
- mock<View>().apply {
- whenever(
- this.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- )
- .thenReturn(chipBackgroundView)
- }
-
private val underTest = kosmos.castToOtherDeviceChipViewModel
@Before
@@ -116,6 +101,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
@@ -223,7 +209,11 @@
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
- MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+ MediaProjectionState.Projecting.SingleTask(
+ NORMAL_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1),
+ )
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@@ -258,6 +248,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
@@ -306,14 +297,8 @@
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockScreenCastDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ clickListener!!.onClick(mock<View>())
+ verify(mockScreenCastDialog).show()
}
@Test
@@ -324,20 +309,15 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockScreenCastDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ clickListener!!.onClick(mock<View>())
+ verify(mockScreenCastDialog).show()
}
@Test
@@ -359,13 +339,7 @@
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockGenericCastDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ clickListener!!.onClick(mock<View>())
+ verify(mockGenericCastDialog).show()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index 7eb4ca0..d0c5e7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -72,6 +72,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1)
)
@@ -101,7 +102,11 @@
val latest by collectLastValue(underTest.projection)
mediaProjectionRepo.mediaProjectionState.value =
- MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+ MediaProjectionState.Projecting.SingleTask(
+ NORMAL_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1),
+ )
assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
assertThat((latest as ProjectionChipModel.Projecting).type)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index ab935fe..c62e404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -72,6 +72,7 @@
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent),
)
@@ -92,6 +93,7 @@
val projectionState =
MediaProjectionState.Projecting.SingleTask(
"host.package",
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
index 83b31c2..6bfb40f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
@@ -103,7 +103,11 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
val task = createTask(taskId = 1)
mediaProjectionRepo.mediaProjectionState.value =
- MediaProjectionState.Projecting.SingleTask("host.package", task)
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ hostDeviceName = null,
+ task,
+ )
assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 0a06cc7..4728c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -19,7 +19,6 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -34,7 +33,6 @@
import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
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.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.util.time.fakeSystemClock
@@ -42,9 +40,7 @@
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.mockito.ArgumentMatchers
import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -58,17 +54,6 @@
private val systemClock = kosmos.fakeSystemClock
private val mockSystemUIDialog = mock<SystemUIDialog>()
- private val chipBackgroundView = mock<ChipBackgroundContainer>()
- private val chipView =
- mock<View>().apply {
- whenever(
- this.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- )
- .thenReturn(chipBackgroundView)
- }
-
private val underTest = kosmos.screenRecordChipViewModel
@Before
@@ -197,15 +182,9 @@
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
+ clickListener!!.onClick(mock<View>())
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockSystemUIDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ verify(mockSystemUIDialog).show()
}
@Test
@@ -219,15 +198,9 @@
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
+ clickListener!!.onClick(mock<View>())
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockSystemUIDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ verify(mockSystemUIDialog).show()
}
@Test
@@ -238,20 +211,15 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
"host.package",
+ hostDeviceName = null,
FakeActivityTaskManager.createTask(taskId = 1)
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
+ clickListener!!.onClick(mock<View>())
// EndScreenRecordingDialogDelegate will test that the dialog has the right message
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockSystemUIDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ verify(mockSystemUIDialog).show()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index bfb57c5..325a42b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -116,6 +116,7 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent)
)
)
@@ -140,6 +141,7 @@
createAndSetDelegate(
MediaProjectionState.Projecting.SingleTask(
HOST_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1, baseIntent = baseIntent)
)
)
@@ -200,7 +202,6 @@
ProjectionChipModel.Projecting(
ProjectionChipModel.Type.SHARE_TO_APP,
state,
- deviceName = null,
),
)
}
@@ -209,6 +210,10 @@
private const val HOST_PACKAGE = "fake.host.package"
private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
private val SINGLE_TASK =
- MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+ MediaProjectionState.Projecting.SingleTask(
+ HOST_PACKAGE,
+ hostDeviceName = null,
+ createTask(taskId = 1)
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 3028d00..f87b17d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -19,7 +19,6 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -35,7 +34,6 @@
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
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.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.util.time.fakeSystemClock
@@ -43,9 +41,7 @@
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.mockito.ArgumentMatchers
import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -59,17 +55,6 @@
private val mockShareDialog = mock<SystemUIDialog>()
- private val chipBackgroundView = mock<ChipBackgroundContainer>()
- private val chipView =
- mock<View>().apply {
- whenever(
- this.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- )
- .thenReturn(chipBackgroundView)
- }
-
private val underTest = kosmos.shareToAppChipViewModel
@Before
@@ -98,6 +83,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
CAST_TO_OTHER_DEVICES_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
@@ -123,6 +109,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
NORMAL_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
@@ -176,6 +163,7 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
NORMAL_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
@@ -193,14 +181,8 @@
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockShareDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ clickListener!!.onClick(mock<View>())
+ verify(mockShareDialog).show()
}
@Test
@@ -210,19 +192,14 @@
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
NORMAL_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
assertThat(clickListener).isNotNull()
- clickListener!!.onClick(chipView)
- verify(kosmos.mockDialogTransitionAnimator)
- .showFromView(
- eq(mockShareDialog),
- eq(chipBackgroundView),
- eq(null),
- ArgumentMatchers.anyBoolean(),
- )
+ clickListener!!.onClick(mock<View>())
+ verify(mockShareDialog).show()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index c9c7359..ca043f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,50 +19,31 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlin.test.Test
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@SmallTest
class OngoingActivityChipViewModelTest : SysuiTestCase() {
private val mockSystemUIDialog = mock<SystemUIDialog>()
private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog }
- private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
-
- private val chipBackgroundView = mock<ChipBackgroundContainer>()
- private val chipView =
- mock<View>().apply {
- whenever(
- this.requireViewById<ChipBackgroundContainer>(
- R.id.ongoing_activity_chip_background
- )
- )
- .thenReturn(chipBackgroundView)
- }
@Test
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
val clickListener =
- createDialogLaunchOnClickListener(dialogDelegate, dialogTransitionAnimator)
+ createDialogLaunchOnClickListener(
+ dialogDelegate,
+ logcatLogBuffer("OngoingActivityChipViewModelTest"),
+ "tag",
+ )
// Dialogs must be created on the main thread
context.mainExecutor.execute {
- clickListener.onClick(chipView)
- verify(dialogTransitionAnimator)
- .showFromView(
- eq(mockSystemUIDialog),
- eq(chipBackgroundView),
- eq(null),
- anyBoolean(),
- )
+ clickListener.onClick(mock<View>())
+ verify(mockSystemUIDialog).show()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 8bc83cf..b1a8d0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -155,6 +155,7 @@
mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
NORMAL_PACKAGE,
+ hostDeviceName = null,
createTask(taskId = 1),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e8349b0..ed7383c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -1003,21 +1003,6 @@
// THEN
assertThat(row.isExpanded()).isFalse();
}
-
- @Test
- @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
- public void hasUserChangedExpansion_expandPinned_returnTrue() throws Exception {
- // GIVEN
- final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPinned(true);
-
- // WHEN
- row.expandNotification();
-
- // THEN
- assertThat(row.hasUserChangedExpansion()).isTrue();
- }
-
@Test
public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 285949a..9eafcdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -495,18 +495,18 @@
}
@Test
- public void biometricUnlockStateResetOnTransitionFromGone() {
- mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState(
+ public void biometricUnlockStateResetOnStartedTransition() {
+ mBiometricUnlockController.consumeFromGoneTransitions(
new TransitionStep(
- KeyguardState.AOD,
KeyguardState.GONE,
+ KeyguardState.AOD,
.1f /* value */,
- TransitionState.STARTED
+ TransitionState.RUNNING
)
);
verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt(), any());
- mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState(
+ mBiometricUnlockController.consumeFromGoneTransitions(
new TransitionStep(
KeyguardState.GONE,
KeyguardState.AOD,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 7cb41f1..5052a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -52,6 +52,8 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Assert;
import org.junit.Before;
@@ -113,6 +115,7 @@
.thenReturn(mFoldAodAnimationController);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ SecureSettings secureSettings = new FakeSettings();
mDozeParameters = new DozeParameters(
mContext,
mHandler,
@@ -130,7 +133,8 @@
mConfigurationController,
mStatusBarStateController,
mUserTracker,
- mDozeInteractor
+ mDozeInteractor,
+ secureSettings
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 30e96f1..e439aff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -738,6 +738,28 @@
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ // See b/346904529 for more context
+ fun satBasedIcon_doesNotInflateSignalStrength() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index cec4155..e510924 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -862,6 +862,38 @@
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satelliteIcon_ignoresInflateSignalStrength() =
+ testScope.runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.value = true
+ repository.isNonTerrestrial.value = true
+ repository.setAllLevels(0)
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
private fun createAndSetViewModel() {
underTest =
MobileIconViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index 59b20c8..627463b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -1,6 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -58,7 +60,8 @@
mController = new CastControllerImpl(
mContext,
mock(PackageManager.class),
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ new CastControllerLogger(logcatLogBuffer("CastControllerImplTest")));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
index 03ad66c..16061df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
@@ -25,6 +25,7 @@
import android.media.projection.MediaProjectionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.CastDevice.Companion.toCastDevice
import com.google.common.truth.Truth.assertThat
@@ -40,6 +41,7 @@
class CastDeviceTest : SysuiTestCase() {
private val mockAppInfo =
mock<ApplicationInfo>().apply { whenever(this.loadLabel(any())).thenReturn("") }
+ private val logger = CastControllerLogger(logcatLogBuffer("CastDeviceTest"))
private val packageManager =
mock<PackageManager>().apply {
@@ -322,7 +324,7 @@
whenever(this.packageName).thenReturn("fake.package")
}
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.id).isEqualTo("fake.package")
}
@@ -334,7 +336,7 @@
whenever(this.packageName).thenReturn(HEADLESS_REMOTE_PACKAGE)
}
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEmpty()
}
@@ -349,7 +351,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenThrow(PackageManager.NameNotFoundException())
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo(NORMAL_PACKAGE)
}
@@ -366,7 +368,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenReturn(appInfo)
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo(NORMAL_PACKAGE)
}
@@ -383,7 +385,7 @@
whenever(packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
.thenReturn(appInfo)
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.name).isEqualTo("Valid App Name")
}
@@ -392,7 +394,7 @@
fun projectionToCastDevice_descriptionIsCasting() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.description).isEqualTo(context.getString(R.string.quick_settings_casting))
}
@@ -401,7 +403,7 @@
fun projectionToCastDevice_stateIsConnected() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.state).isEqualTo(CastDevice.CastState.Connected)
}
@@ -410,7 +412,7 @@
fun projectionToCastDevice_tagIsProjection() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.tag).isEqualTo(projection)
}
@@ -419,7 +421,7 @@
fun projectionToCastDevice_originIsMediaProjection() {
val projection = mockProjectionInfo()
- val device = projection.toCastDevice(context, packageManager)
+ val device = projection.toCastDevice(context, packageManager, logger)
assertThat(device.origin).isEqualTo(CastDevice.CastOrigin.MediaProjection)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ef4e734..cc2ef53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,6 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -126,6 +128,7 @@
}
@Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
fun isVisible_headsUpStatusBarShown_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index cf0db7b..ce6bc09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -29,9 +29,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+@Ignore
@SmallTest
@RunWith(AndroidJUnit4::class)
class BackGestureMonitorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 769f264..f5ef8b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -34,9 +34,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+@Ignore
@SmallTest
@RunWith(AndroidJUnit4::class)
class TouchpadGestureHandlerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
deleted file mode 100644
index bb7b31b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.LeakCheck.Tracker;
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TunablePaddingTest extends LeakCheckedTest {
-
- private static final String KEY = "KEY";
- private static final int DEFAULT = 42;
- private View mView;
- private TunablePadding mTunablePadding;
- private TunerService mTunerService;
-
- @Before
- public void setup() {
- injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
- mView = mock(View.class);
- when(mView.getContext()).thenReturn(mContext);
-
- mTunerService = mock(TunerService.class);
- mDependency.injectTestDependency(TunablePadding.TunablePaddingService.class,
- new TunablePadding.TunablePaddingService(mTunerService));
- Tracker tracker = mLeakCheck.getTracker("tuner");
- doAnswer(invocation -> {
- tracker.getLeakInfo(invocation.getArguments()[0]).addAllocation(new Throwable());
- return null;
- }).when(mTunerService).addTunable(any(), any());
- doAnswer(invocation -> {
- tracker.getLeakInfo(invocation.getArguments()[0]).clearAllocations();
- return null;
- }).when(mTunerService).removeTunable(any());
- }
-
- @Test
- public void testFlags() {
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_START);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
- mTunablePadding.destroy();
-
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_TOP);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(0), eq(DEFAULT), eq(0), eq(0));
- mTunablePadding.destroy();
-
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_END);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
- mTunablePadding.destroy();
-
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_BOTTOM);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(0), eq(0), eq(0), eq(DEFAULT));
- mTunablePadding.destroy();
- }
-
- @Test
- public void testRtl() {
- when(mView.isLayoutRtl()).thenReturn(true);
-
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_END);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
- mTunablePadding.destroy();
-
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_START);
- mTunablePadding.onTuningChanged(null, null);
- verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
- mTunablePadding.destroy();
- }
-
- @Test
- public void testTuning() {
- int value = 3;
- mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
- TunablePadding.FLAG_START);
- mTunablePadding.onTuningChanged(KEY, String.valueOf(value));
-
- DisplayMetrics metrics = new DisplayMetrics();
- mContext.getSystemService(WindowManager.class).getDefaultDisplay().getMetrics(metrics);
- int output = (int) (metrics.density * value);
- verify(mView).setPadding(eq(output), eq(0), eq(0), eq(0));
-
- mTunablePadding.destroy();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index dd791e7..5ac6110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -28,9 +28,10 @@
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -44,6 +45,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
class SettingsProxyTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
@@ -60,11 +62,12 @@
}
@Test
- fun registerContentObserver_inputString_success() {
- mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
- }
+ fun registerContentObserver_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+ }
@Test
fun registerContentObserverSuspend_inputString_success() =
@@ -75,24 +78,25 @@
}
@Test
- fun registerContentObserverAsync_inputString_success() {
- mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver)
- testScope.launch {
+ fun registerContentObserverAsync_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver)
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
}
- }
@Test
- fun registerContentObserver_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverSync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
- }
+ fun registerContentObserver_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+ }
@Test
fun registerContentObserverSuspend_inputString_notifyForDescendants_true() =
@@ -107,24 +111,25 @@
}
@Test
- fun registerContentObserverAsync_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverAsync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver
- )
- testScope.launch {
+ fun registerContentObserverAsync_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
}
- }
@Test
- fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
- }
+ fun registerContentObserver_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+ }
@Test
fun registerContentObserverSuspend_inputUri_success() =
@@ -135,24 +140,25 @@
}
@Test
- fun registerContentObserverAsync_inputUri_success() {
- mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
- testScope.launch {
+ fun registerContentObserverAsync_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
}
- }
@Test
- fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
- }
+ fun registerContentObserver_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+ }
@Test
fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
@@ -167,23 +173,56 @@
}
@Test
- fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverAsync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver
- )
- testScope.launch {
+ fun registerContentObserverAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
}
+
+ @Test
+ fun registerContentObserverAsync_registeredLambdaPassed_callsCallback() =
+ testScope.runTest {
+ verifyRegisteredCallbackForRegistration {
+ mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver, it)
+ }
+ verifyRegisteredCallbackForRegistration {
+ mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver, it)
+ }
+ verifyRegisteredCallbackForRegistration {
+ mSettings.registerContentObserverAsync(TEST_SETTING, false, mContentObserver, it)
+ }
+ verifyRegisteredCallbackForRegistration {
+ mSettings.registerContentObserverAsync(
+ TEST_SETTING_URI,
+ false,
+ mContentObserver,
+ it
+ )
+ }
+ }
+
+ private fun verifyRegisteredCallbackForRegistration(
+ call: (registeredRunnable: Runnable) -> Unit
+ ) {
+ var callbackCalled = false
+ val runnable = { callbackCalled = true }
+ call(runnable)
+ testScope.advanceUntilIdle()
+ assertThat(callbackCalled).isTrue()
}
@Test
- fun unregisterContentObserverSync() {
- mSettings.unregisterContentObserverSync(mContentObserver)
- verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
- }
+ fun unregisterContentObserverSync() =
+ testScope.runTest {
+ mSettings.unregisterContentObserverSync(mContentObserver)
+ verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+ }
@Test
fun unregisterContentObserverSuspend_inputString_success() =
@@ -193,12 +232,12 @@
}
@Test
- fun unregisterContentObserverAsync_inputString_success() {
- mSettings.unregisterContentObserverAsync(mContentObserver)
- testScope.launch {
+ fun unregisterContentObserverAsync_inputString_success() =
+ testScope.runTest {
+ mSettings.unregisterContentObserverAsync(mContentObserver)
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
}
- }
@Test
fun getString_keyPresent_returnValidValue() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index e3e20c8..5f7420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -31,9 +31,11 @@
import com.android.systemui.settings.UserTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -65,20 +67,21 @@
}
@Test
- fun registerContentObserverForUser_inputString_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_success() =
@@ -98,13 +101,14 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -113,24 +117,24 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_notifyForDescendants_true() =
@@ -153,14 +157,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -169,23 +174,23 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputUri_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_success() =
@@ -205,13 +210,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
+
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -220,24 +227,41 @@
eq(MAIN_USER_ID)
)
}
- }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun registerContentObserverForUserAsync_callbackAfterRegister() =
+ testScope.runTest {
+ var callbackCalled = false
+ val runnable = { callbackCalled = true }
+
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId,
+ runnable
+ )
+ testScope.advanceUntilIdle()
+ assertThat(callbackCalled).isTrue()
+ }
@Test
- fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_notifyForDescendants_true() =
@@ -260,14 +284,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -276,14 +301,19 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_success() =
@@ -313,15 +343,21 @@
}
@Test
- fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
@@ -337,18 +373,19 @@
}
@Test
- fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
- testScope.launch {
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(0)
- )
+ fun registerContentObserverAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+ testScope.launch {
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
}
- }
@Test
fun getString_keyPresent_returnValidValue() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index c81623e..49aedcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -19,28 +19,46 @@
import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.Notification;
import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.media.AudioManager;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
+import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@@ -48,41 +66,109 @@
private NotificationManager mNotificationManager;
private AudioManager mAudioManager;
+ private BroadcastDispatcher mFakeBroadcastDispatcher;
+ private CsdWarningDialog mDialog;
+ private static final String DISMISS_CSD_NOTIFICATION =
+ "com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
@Before
public void setup() {
mNotificationManager = mock(NotificationManager.class);
- getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+ mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
mAudioManager = mock(AudioManager.class);
- getContext().addMockSystemService(AudioManager.class, mAudioManager);
+ mContext.addMockSystemService(AudioManager.class, mAudioManager);
+ mFakeBroadcastDispatcher = getFakeBroadcastDispatcher();
}
@Test
public void create1XCsdDialogAndWait_sendsNotification() {
FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
// instantiate directly instead of via factory; we don't want executor to be @Background
- CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
- mAudioManager, mNotificationManager, executor, null);
+ mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+ mAudioManager, mNotificationManager, executor, null,
+ Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mFakeBroadcastDispatcher);
- dialog.show();
+ mDialog.show();
executor.advanceClockToLast();
executor.runAllReady();
- dialog.dismiss();
+ mDialog.dismiss();
verify(mNotificationManager).notify(
eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
}
@Test
- public void create5XCsdDiSalogAndWait_willSendNotification() {
+ public void create5XCsdDialogAndWait_willSendNotification() {
FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
- CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
- mAudioManager, mNotificationManager, executor, null);
+ mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+ mAudioManager, mNotificationManager, executor, null,
+ Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mFakeBroadcastDispatcher);
- dialog.show();
+ mDialog.show();
verify(mNotificationManager).notify(
eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_SOUNDDOSE_CUSTOMIZATION)
+ public void create1XCsdDialogWithActionsAndUndoIntent_willRegisterReceiverAndUndoVolume() {
+ FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
+ Intent undoIntent = new Intent(VolumeDialog.ACTION_VOLUME_UNDO)
+ .setPackage(mContext.getPackageName());
+ mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+ mAudioManager, mNotificationManager, executor, null,
+ Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ mFakeBroadcastDispatcher);
+
+ when(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+ mDialog.show();
+ executor.advanceClockToLast();
+ executor.runAllReady();
+ mDialog.dismiss();
+ mDialog.mReceiverUndo.onReceive(mContext, undoIntent);
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO),
+ any(Notification.class));
+ verify(mAudioManager).setStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(25),
+ eq(AudioManager.FLAG_SHOW_UI));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SOUNDDOSE_CUSTOMIZATION)
+ public void deleteNotificationIntent_willUnregisterAllReceivers() {
+ FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
+ Intent undoIntent = new Intent(VolumeDialog.ACTION_VOLUME_UNDO)
+ .setPackage(mContext.getPackageName());
+ mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+ mAudioManager, mNotificationManager, executor, null,
+ Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ mFakeBroadcastDispatcher);
+ Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+ .setPackage(mContext.getPackageName());
+
+ mDialog.mReceiverDismissNotification.onReceive(mContext, dismissIntent);
+ mDialog.show();
+ executor.advanceClockToLast();
+ executor.runAllReady();
+ mDialog.dismiss();
+
+ List<ResolveInfo> resolveInfoListDismiss = mContext.getPackageManager()
+ .queryBroadcastReceivers(dismissIntent, PackageManager.GET_RESOLVED_FILTER);
+ assertThat(resolveInfoListDismiss).hasSize(0);
+ List<ResolveInfo> resolveInfoListUndo = mContext.getPackageManager()
+ .queryBroadcastReceivers(undoIntent, PackageManager.GET_RESOLVED_FILTER);
+ assertThat(resolveInfoListUndo).hasSize(0);
+ }
+
+ @After
+ public void tearDown() {
+ mDialog.destroy();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 6efb7d8..cdfcca6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -43,6 +43,7 @@
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -55,6 +56,7 @@
import android.provider.Settings;
import android.testing.TestableLooper;
import android.util.Log;
+import android.util.Pair;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -92,6 +94,8 @@
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
+import com.google.common.collect.ImmutableList;
+
import dagger.Lazy;
import junit.framework.Assert;
@@ -107,6 +111,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
+import java.util.Optional;
import java.util.function.Predicate;
@SmallTest
@@ -157,11 +162,12 @@
private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
new CsdWarningDialog.Factory() {
- @Override
- public CsdWarningDialog create(int warningType, Runnable onCleanup) {
- return mCsdWarningDialog;
- }
- };
+ @Override
+ public CsdWarningDialog create(int warningType, Runnable onCleanup,
+ Optional<ImmutableList<Pair<String, Intent>>> actionIntents) {
+ return mCsdWarningDialog;
+ }
+ };
@Mock
private VibratorHelper mVibratorHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index dc7a2c3..6c4608d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -192,6 +192,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -1852,6 +1853,7 @@
any(Bubble.class), anyBoolean(), anyBoolean());
}
+ @Ignore("reason = b/351977103")
@Test
public void testShowStackEdu_isNotConversationBubble() {
// Setup
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
similarity index 80%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
index e40152a..796ec94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.data.repository
+package android.hardware.display
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+val Kosmos.displayManager by Kosmos.Fixture { mock<DisplayManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 7c53639..0f8833c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui
import android.app.ActivityManager
+import android.app.DreamManager
import android.app.admin.DevicePolicyManager
import android.app.trust.TrustManager
import android.hardware.fingerprint.FingerprintManager
@@ -33,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -94,6 +96,7 @@
@get:Provides val demoModeController: DemoModeController = mock(),
@get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
@get:Provides val dozeParameters: DozeParameters = mock(),
+ @get:Provides val dreamManager: DreamManager = mock(),
@get:Provides val dumpManager: DumpManager = mock(),
@get:Provides val fingerprintManager: FingerprintManager = mock(),
@get:Provides val headsUpManager: HeadsUpManager = mock(),
@@ -132,6 +135,7 @@
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
+ @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
@get:Provides val trustManager: TrustManager = mock(),
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
index 8b0affe2..e02042d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility
+import android.content.res.Resources
+import com.android.server.display.feature.flags.Flags
import com.android.systemui.qs.ReduceBrightColorsController
class FakeReduceBrightColorsController : ReduceBrightColorsController {
@@ -44,4 +46,20 @@
}
}
}
+
+ override fun setReduceBrightColorsFeatureAvailable(enabled: Boolean) {
+ // do nothing
+ }
+
+ override fun isReduceBrightColorsFeatureAvailable(): Boolean {
+ return true
+ }
+
+ override fun isInUpgradeMode(resources: Resources?): Boolean {
+ if (resources != null) {
+ return Flags.evenDimmer() &&
+ resources.getBoolean(com.android.internal.R.bool.config_evenDimmerEnabled)
+ }
+ return false
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index 4b64416..de5f0f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
@@ -44,7 +44,7 @@
clock = systemClock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = deviceEntryFingerprintAuthInteractor,
flags = composeBouncerFlags,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
index e40152a..8d01fcd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.data.repository
+package com.android.systemui.communal.data.db
import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+val Kosmos.defaultWidgetPopulation by
+ Kosmos.Fixture<DefaultWidgetPopulation> { mock(DefaultWidgetPopulation::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
index e40152a..559a6ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.data.repository
+package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+val Kosmos.fakeCommunalSmartspaceRepository by Kosmos.Fixture { FakeCommunalSmartspaceRepository() }
+
+val Kosmos.communalSmartspaceRepository by
+ Kosmos.Fixture<CommunalSmartspaceRepository> { fakeCommunalSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 1884a32..14b1984 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -36,4 +36,18 @@
fun mediaInactive() {
_mediaModel.value = CommunalMediaModel.INACTIVE
}
+
+ private var isListening = false
+
+ override fun startListening() {
+ isListening = true
+ }
+
+ override fun stopListening() {
+ isListening = false
+ }
+
+ fun isListening(): Boolean {
+ return isListening
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt
new file mode 100644
index 0000000..904ab4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeCommunalSmartspaceRepository : CommunalSmartspaceRepository {
+
+ private val _timers = MutableStateFlow<List<CommunalSmartspaceTimer>>(emptyList())
+ override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
+
+ fun setTimers(timers: List<CommunalSmartspaceTimer>) {
+ _timers.value = timers
+ }
+
+ private var isListening = false
+
+ override fun startListening() {
+ isListening = true
+ }
+
+ override fun stopListening() {
+ isListening = false
+ }
+
+ fun isListening(): Boolean {
+ return isListening
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index b58861b..eb92785 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -19,6 +19,7 @@
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalSmartspaceRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.flags.Flags
@@ -34,7 +35,6 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.userTracker
-import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -47,7 +47,7 @@
widgetRepository = communalWidgetRepository,
communalPrefsInteractor = communalPrefsInteractor,
mediaRepository = communalMediaRepository,
- smartspaceRepository = smartspaceRepository,
+ smartspaceRepository = communalSmartspaceRepository,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
communalSettingsInteractor = communalSettingsInteractor,
@@ -64,13 +64,17 @@
val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
-suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
- fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, available)
- if (available) {
+suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
+ fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
+ if (enabled) {
fakeUserRepository.asMainUser()
} else {
fakeUserRepository.asDefaultUser()
}
+}
+
+suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
+ setCommunalEnabled(available)
with(fakeKeyguardRepository) {
setIsEncryptedOrLockdown(!available)
setKeyguardShowing(available)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 25e7729..045bd5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -30,16 +30,10 @@
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
- var userPresentCount = 0
-
override suspend fun isLockscreenEnabled(): Boolean {
return isLockscreenEnabled
}
- override suspend fun reportUserPresent() {
- userPresentCount++
- }
-
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
this.isLockscreenEnabled = isLockscreenEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1200866..caa6e99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -19,8 +19,6 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,12 +32,7 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
- trustInteractor = trustInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
- systemPropertiesHelper = fakeSystemPropertiesHelper,
alternateBouncerInteractor = alternateBouncerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 14210bc..1ed10fbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.flags.systemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -33,5 +35,7 @@
faceAuthInteractor = deviceEntryFaceAuthInteractor,
fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
powerInteractor = powerInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index e40152a..f73f43d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.data.repository
+package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
+import java.time.Instant
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+ Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
new file mode 100644
index 0000000..5410882
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.shared.education.GestureType
+import java.time.Clock
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+
+ private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
+ private val _gestureEduModels = MutableStateFlow(GestureEduModel())
+ private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ override fun setUser(userId: Int) {
+ if (!userGestureMap.contains(userId)) {
+ userGestureMap[userId] = GestureEduModel()
+ }
+ _gestureEduModels.value = userGestureMap[userId]!!
+ }
+
+ override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
+ return gestureEduModelsFlow
+ }
+
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
+ _gestureEduModels.value =
+ GestureEduModel(
+ signalCount = _gestureEduModels.value.signalCount + 1,
+ )
+ }
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
new file mode 100644
index 0000000..513c143
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneId
+
+class FakeEduClock(private val base: Instant) : Clock() {
+ private val zone: ZoneId = ZoneId.of("UTC")
+
+ override fun instant(): Instant {
+ return base
+ }
+
+ override fun withZone(zoneId: ZoneId?): Clock {
+ return FakeEduClock(base)
+ }
+
+ override fun getZone(): ZoneId {
+ return zone
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index eff99e04..28355e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,9 +16,10 @@
package com.android.systemui.haptics.qs
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController, falsingManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index f436a68..001b55b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
@@ -67,18 +68,23 @@
)
}
-val Kosmos.shortcutHelperInputShortcutsSource by
+var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) }
+var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
+ Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
+
val Kosmos.shortcutHelperCategoriesRepository by
Kosmos.Fixture {
ShortcutHelperCategoriesRepository(
applicationContext,
+ applicationCoroutineScope,
testDispatcher,
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
shortcutHelperAppCategoriesShortcutsSource,
shortcutHelperInputShortcutsSource,
+ shortcutHelperCurrentAppShortcutsSource,
fakeInputManager.inputManager,
shortcutHelperStateRepository,
)
@@ -91,7 +97,8 @@
applicationContext,
broadcastDispatcher,
fakeCommandQueue,
- windowManager
+ fakeInputManager,
+ windowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 40510db..6ca5cd8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.hardware.input.FakeInputManager
import android.view.KeyboardShortcutGroup
import android.view.WindowManager
import android.view.WindowManager.KeyboardShortcutsReceiver
@@ -31,6 +32,7 @@
private val context: Context,
private val fakeBroadcastDispatcher: FakeBroadcastDispatcher,
private val fakeCommandQueue: FakeCommandQueue,
+ private val fakeInputManager: FakeInputManager,
windowManager: WindowManager
) {
@@ -39,6 +41,7 @@
}
private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList()
+ private var currentAppsShortcuts: List<KeyboardShortcutGroup> = emptyList()
init {
whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
@@ -46,6 +49,11 @@
keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts)
return@thenAnswer Unit
}
+ whenever(windowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+ val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0)
+ keyboardShortcutReceiver.onKeyboardShortcutsReceived(currentAppsShortcuts)
+ return@thenAnswer Unit
+ }
repo.start()
}
@@ -57,6 +65,14 @@
this.imeShortcuts = imeShortcuts
}
+ /**
+ * Use this method to set what current app shortcuts should be returned from windowManager in
+ * tests. By default [WindowManager.requestAppKeyboardShortcuts] will return emptyList.
+ */
+ fun setCurrentAppsShortcuts(currentAppShortcuts: List<KeyboardShortcutGroup>) {
+ this.currentAppsShortcuts = currentAppShortcuts
+ }
+
fun hideThroughCloseSystemDialogs() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
@@ -79,6 +95,7 @@
}
fun toggle(deviceId: Int) {
+ fakeInputManager.addPhysicalKeyboard(deviceId)
fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
}
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 446652c..126d858 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
@@ -16,7 +16,9 @@
package com.android.systemui.keyguard.domain.interactor
+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.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -36,9 +38,11 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
+ dreamManager = dreamManager
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index 4328ca1..406b5cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -16,11 +16,9 @@
package com.android.systemui.keyguard.domain.interactor
-import android.content.applicationContext
import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
-import com.android.systemui.keyguard.data.repository.keyguardClockSection
import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -31,12 +29,9 @@
KeyguardBlueprintInteractor(
keyguardBlueprintRepository = keyguardBlueprintRepository,
applicationScope = applicationCoroutineScope,
- context = applicationContext,
shadeInteractor = shadeInteractor,
- clockInteractor = keyguardClockInteractor,
configurationInteractor = configurationInteractor,
fingerprintPropertyInteractor = fingerprintPropertyInteractor,
- clockSection = keyguardClockSection,
smartspaceSection = keyguardSmartspaceSection,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 03e5a90..2c6d44f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -19,7 +19,6 @@
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.sceneInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -30,6 +29,5 @@
transitionInteractor = keyguardTransitionInteractor,
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
- sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..b5f0b89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+var Kosmos.dreamingToAodTransitionViewModel by Fixture {
+ DreamingToAodTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 2567ffe..3c5baa5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -46,6 +46,7 @@
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
index f253e94..0412274 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt
@@ -16,10 +16,12 @@
package com.android.systemui.mediaprojection.data.repository
+import android.hardware.display.displayManager
import android.os.Handler
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
@@ -30,10 +32,12 @@
Kosmos.Fixture {
MediaProjectionManagerRepository(
mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ displayManager = displayManager,
handler = Handler.getMain(),
applicationScope = applicationCoroutineScope,
tasksRepository = activityTaskManagerTasksRepository,
backgroundDispatcher = testDispatcher,
mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ logger = logcatLogBuffer("TestMediaProjection"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
index eec9920..e1ecc51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.policy.fakeCastController
val Kosmos.realMediaRouterRepository by
@@ -25,6 +26,7 @@
MediaRouterRepositoryImpl(
scope = applicationCoroutineScope,
castController = fakeCastController,
+ logger = logcatLogBuffer("MediaRouter"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
similarity index 84%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
index e40152a..92ac089 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/DefaultLargeTilesRepositoryKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.defaultLargeTilesRepository: DefaultLargeTilesRepository by
+ Kosmos.Fixture { DefaultLargeTilesRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
index 39ae5eb..513d4e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
@@ -22,4 +22,11 @@
import com.android.systemui.user.data.repository.userRepository
val Kosmos.qsPreferencesRepository by
- Kosmos.Fixture { QSPreferencesRepository(userFileManager, userRepository, testDispatcher) }
+ Kosmos.Fixture {
+ QSPreferencesRepository(
+ userFileManager,
+ userRepository,
+ defaultLargeTilesRepository,
+ testDispatcher
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index eaa702f..76dccdb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -17,6 +17,16 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-val Kosmos.iconTilesInteractor by Kosmos.Fixture { IconTilesInteractor(iconTilesRepository) }
+val Kosmos.iconTilesInteractor by
+ Kosmos.Fixture {
+ IconTilesInteractor(
+ defaultLargeTilesRepository,
+ qsPreferencesInteractor,
+ FakeLogBuffer.Factory.create(),
+ applicationCoroutineScope
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index b9918f1..4660337 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,7 +18,6 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.shared.model.ShadeMode
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -30,53 +29,65 @@
@SysUISingleton
class FakeShadeRepository @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
- @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion = _qsExpansion
+
+ @Deprecated("Use ShadeInteractor.qsExpansion instead")
+ override val qsExpansion = _qsExpansion.asStateFlow()
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
- override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
+ override val udfpsTransitionToFullShadeProgress =
+ _udfpsTransitionToFullShadeProgress.asStateFlow()
private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
- override val currentFling: StateFlow<FlingInfo?> = _currentFling
+ override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow()
private val _lockscreenShadeExpansion = MutableStateFlow(0f)
- override val lockscreenShadeExpansion = _lockscreenShadeExpansion
+ override val lockscreenShadeExpansion = _lockscreenShadeExpansion.asStateFlow()
private val _legacyShadeExpansion = MutableStateFlow(0f)
+
@Deprecated("Use ShadeInteractor instead")
- override val legacyShadeExpansion = _legacyShadeExpansion
+ override val legacyShadeExpansion = _legacyShadeExpansion.asStateFlow()
private val _legacyShadeTracking = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead")
- override val legacyShadeTracking = _legacyShadeTracking
+ override val legacyShadeTracking = _legacyShadeTracking.asStateFlow()
private val _legacyQsTracking = MutableStateFlow(false)
- @Deprecated("Use ShadeInteractor instead") override val legacyQsTracking = _legacyQsTracking
+
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyQsTracking = _legacyQsTracking.asStateFlow()
private val _legacyExpandedOrAwaitingInputTransfer = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead")
- override val legacyExpandedOrAwaitingInputTransfer = _legacyExpandedOrAwaitingInputTransfer
+ override val legacyExpandedOrAwaitingInputTransfer =
+ _legacyExpandedOrAwaitingInputTransfer.asStateFlow()
private val _legacyIsQsExpanded = MutableStateFlow(false)
- @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyIsQsExpanded = _legacyIsQsExpanded.asStateFlow()
@Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
- private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
- override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
-
private var _isDualShadeAlignedToBottom = false
override val isDualShadeAlignedToBottom
get() = _isDualShadeAlignedToBottom
+ private var _isShadeLayoutWide = MutableStateFlow(false)
+ override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
_legacyIsQsExpanded.value = legacyIsQsExpanded
}
private val _legacyExpandImmediate = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead")
- override val legacyExpandImmediate = _legacyExpandImmediate
+ override val legacyExpandImmediate = _legacyExpandImmediate.asStateFlow()
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
@@ -106,6 +117,7 @@
}
private val _legacyQsFullscreen = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen = _legacyQsFullscreen
@Deprecated("Use ShadeInteractor instead")
@@ -114,6 +126,7 @@
}
private val _legacyIsClosing = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead") override val legacyIsClosing = _legacyIsClosing
@Deprecated("Use ShadeInteractor instead")
@@ -142,13 +155,13 @@
_legacyShadeExpansion.value = expandedFraction
}
- override fun setShadeMode(mode: ShadeMode) {
- _shadeMode.value = mode
- }
-
fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
_isDualShadeAlignedToBottom = isAlignedToBottom
}
+
+ override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
+ _isShadeLayoutWide.value = isShadeLayoutWide
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index a00d2f4..bfd6614 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -45,7 +45,6 @@
scope = applicationCoroutineScope,
sceneInteractor = sceneInteractor,
sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
- shadeRepository = shadeRepository,
)
}
val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
deleted file mode 100644
index 862e52d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.systemui.smartspace.data.repository
-
-import android.app.smartspace.SmartspaceTarget
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSmartspaceRepository(
- smartspaceRemoteViewsEnabled: Boolean = true,
-) : SmartspaceRepository {
-
- override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled
-
- private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
- MutableStateFlow(emptyList())
- override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
- _communalSmartspaceTargets
-
- fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) {
- _communalSmartspaceTargets.value = targets
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
deleted file mode 100644
index 0e4c923..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
+++ /dev/null
@@ -1,24 +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.smartspace.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-
-val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() }
-
-val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index ab71b5e..1e304d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.chips.call.domain.interactor.callChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.callChipViewModel: CallChipViewModel by
@@ -29,5 +30,6 @@
interactor = callChipInteractor,
systemClock = fakeSystemClock,
activityStarter = activityStarter,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
index cb18b68..1737bc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
@@ -19,11 +19,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.mediaRouterChipInteractor by
Kosmos.Fixture {
MediaRouterChipInteractor(
scope = applicationCoroutineScope,
mediaRouterRepository = fakeMediaRouterRepository,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 144fe26..3d85a4a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -17,12 +17,12 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by
@@ -34,6 +34,6 @@
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
- dialogTransitionAnimator = mockDialogTransitionAnimator,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 1d06947..e4bb166 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -17,11 +17,11 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by
@@ -31,7 +31,7 @@
context = applicationContext,
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
- dialogTransitionAnimator = mockDialogTransitionAnimator,
systemClock = fakeSystemClock,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 2e475a3..8ed7f96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -17,11 +17,11 @@
package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by
@@ -32,6 +32,6 @@
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
- dialogTransitionAnimator = mockDialogTransitionAnimator,
+ logger = statusBarChipsLogger,
)
}
diff --git a/services/Android.bp b/services/Android.bp
index cd974c5..dce6aa7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -186,7 +186,15 @@
// merge all required services into one jar
// ============================================================
-java_library {
+soong_config_module_type {
+ name: "system_java_library",
+ module_type: "java_library",
+ config_namespace: "system_services",
+ bool_variables: ["without_vibrator"],
+ properties: ["vintf_fragments"],
+}
+
+system_java_library {
name: "services",
defaults: [
"services_java_defaults",
@@ -248,9 +256,19 @@
"service-sdksandbox.stubs.system_server",
],
- vintf_fragments: [
- "manifest_services.xml",
- ],
+ soong_config_variables: {
+ without_vibrator: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ ],
+ conditions_default: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ "manifest_services_android.frameworks.vibrator.xml",
+ ],
+ },
+ },
+ },
required: [
"libukey2_jni_shared",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9fc64a9..099cb28 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,7 +26,6 @@
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
-import android.hardware.input.InputManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -56,7 +55,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -748,8 +746,6 @@
if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
- Objects.requireNonNull(mContext.getSystemService(
- InputManager.class)),
Looper.myLooper(),
Display.DEFAULT_DISPLAY);
addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index b061065..3706dcc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -234,6 +234,8 @@
mAccessibilityShortcutKeyTargets.clear();
mAccessibilityButtonTargets.clear();
mAccessibilityGestureTargets.clear();
+ mAccessibilityQsTargets.clear();
+ mA11yTilesInQsPanel.clear();
mTargetAssignedToAccessibilityButton = null;
mIsTouchExplorationEnabled = false;
mServiceHandlesDoubleTap = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 3f0f23f..56da231 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -23,7 +23,6 @@
import android.annotation.RequiresPermission;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
-import android.hardware.input.InputManager;
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseConfig;
@@ -60,8 +59,8 @@
* In case multiple physical keyboard are connected to a device,
* mouse keys of each physical keyboard will control a single (global) mouse pointer.
*/
-public class MouseKeysInterceptor extends BaseEventStreamTransformation implements Handler.Callback,
- InputManager.InputDeviceListener {
+public class MouseKeysInterceptor extends BaseEventStreamTransformation
+ implements Handler.Callback {
private static final String LOG_TAG = "MouseKeysInterceptor";
// To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
@@ -77,11 +76,8 @@
private static final int INTERVAL_MILLIS = 10;
private final AccessibilityManagerService mAms;
- private final InputManager mInputManager;
private final Handler mHandler;
- private final int mDisplayId;
-
VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
private VirtualMouse mVirtualMouse = null;
@@ -100,23 +96,23 @@
/** Last time the key action was performed */
private long mLastTimeKeyActionPerformed = 0;
- // TODO (b/346706749): This is currently using the numpad key bindings for mouse keys.
- // Decide the final mouse key bindings with UX input.
+ /** Whether scroll toggle is on */
+ private boolean mScrollToggleOn = false;
+
public enum MouseKeyEvent {
- DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_1),
- DOWN_MOVE(KeyEvent.KEYCODE_NUMPAD_2),
- DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_3),
- LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_4),
- RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_6),
- DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_7),
- UP_MOVE(KeyEvent.KEYCODE_NUMPAD_8),
- DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_9),
- LEFT_CLICK(KeyEvent.KEYCODE_NUMPAD_5),
- RIGHT_CLICK(KeyEvent.KEYCODE_NUMPAD_DOT),
- HOLD(KeyEvent.KEYCODE_NUMPAD_MULTIPLY),
- RELEASE(KeyEvent.KEYCODE_NUMPAD_SUBTRACT),
- SCROLL_UP(KeyEvent.KEYCODE_A),
- SCROLL_DOWN(KeyEvent.KEYCODE_S);
+ DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
+ UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
+ DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9),
+ LEFT_MOVE(KeyEvent.KEYCODE_U),
+ RIGHT_MOVE(KeyEvent.KEYCODE_O),
+ DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J),
+ DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K),
+ DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L),
+ LEFT_CLICK(KeyEvent.KEYCODE_I),
+ RIGHT_CLICK(KeyEvent.KEYCODE_SLASH),
+ HOLD(KeyEvent.KEYCODE_M),
+ RELEASE(KeyEvent.KEYCODE_COMMA),
+ SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD);
private final int mKeyCode;
MouseKeyEvent(int enumValue) {
@@ -149,22 +145,19 @@
* Construct a new MouseKeysInterceptor.
*
* @param service The service to notify of key events
- * @param inputManager InputManager to track changes to connected input devices
* @param looper Looper to use for callbacks and messages
* @param displayId Display ID to send mouse events to
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager,
- Looper looper, int displayId) {
+ public MouseKeysInterceptor(AccessibilityManagerService service, Looper looper, int displayId) {
mAms = service;
- mInputManager = inputManager;
mHandler = new Handler(looper, this);
- mInputManager.registerInputDeviceListener(this, mHandler);
- mDisplayId = displayId;
// Create the virtual mouse on a separate thread since virtual device creation
// should happen on an auxiliary thread, and not from the handler's thread.
+ // This is because virtual device creation is a blocking operation and can cause a
+ // deadlock if it is called from the handler's thread.
new Thread(() -> {
- mVirtualMouse = createVirtualMouse();
+ mVirtualMouse = createVirtualMouse(displayId);
}).start();
}
@@ -193,22 +186,23 @@
/**
* Performs a mouse scroll action based on the provided key code.
+ * The scroll action will only be performed if the scroll toggle is on.
* This method interprets the key code as a mouse scroll and sends
* the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}.
* @param keyCode The key code representing the mouse scroll action.
* Supported keys are:
* <ul>
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_UP}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_DOWN}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
* </ul>
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void performMouseScrollAction(int keyCode) {
MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
float y = switch (mouseKeyEvent) {
- case SCROLL_UP -> 1.0f;
- case SCROLL_DOWN -> -1.0f;
+ case UP_MOVE_OR_SCROLL -> 1.0f;
+ case DOWN_MOVE_OR_SCROLL -> -1.0f;
default -> 0.0f;
};
if (mVirtualMouse != null) {
@@ -231,8 +225,8 @@
* @param keyCode The key code representing the mouse button action.
* Supported keys are:
* <ul>
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT_CLICK} (Primary Button)
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT_CLICK} (Secondary
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_CLICK} (Primary Button)
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_CLICK} (Secondary
* Button)
* </ul>
*/
@@ -264,17 +258,20 @@
* The method calculates the relative movement of the mouse pointer
* and sends the corresponding event to the virtual mouse.
*
+ * The UP and DOWN pointer actions will only take place for their respective keys
+ * if the scroll toggle is off.
+ *
* @param keyCode The key code representing the direction or button press.
* Supported keys are:
* <ul>
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_LEFT}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent DOWN}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_RIGHT}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_LEFT}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent UP}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_RIGHT}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE}
* </ul>
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -287,8 +284,10 @@
x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
}
- case DOWN_MOVE -> {
- y = MOUSE_POINTER_MOVEMENT_STEP;
+ case DOWN_MOVE_OR_SCROLL -> {
+ if (!mScrollToggleOn) {
+ y = MOUSE_POINTER_MOVEMENT_STEP;
+ }
}
case DIAGONAL_DOWN_RIGHT_MOVE -> {
x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -304,8 +303,10 @@
x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
}
- case UP_MOVE -> {
- y = -MOUSE_POINTER_MOVEMENT_STEP;
+ case UP_MOVE_OR_SCROLL -> {
+ if (!mScrollToggleOn) {
+ y = -MOUSE_POINTER_MOVEMENT_STEP;
+ }
}
case DIAGONAL_UP_RIGHT_MOVE -> {
x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -333,8 +334,8 @@
}
private boolean isMouseScrollKey(int keyCode) {
- return keyCode == MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
- || keyCode == MouseKeyEvent.SCROLL_DOWN.getKeyCodeValue();
+ return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCodeValue()
+ || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCodeValue();
}
/**
@@ -343,7 +344,7 @@
* @return The created VirtualMouse.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- private VirtualMouse createVirtualMouse() {
+ private VirtualMouse createVirtualMouse(int displayId) {
final VirtualDeviceManagerInternal localVdm =
LocalServices.getService(VirtualDeviceManagerInternal.class);
mVirtualDevice = localVdm.createVirtualDevice(
@@ -351,7 +352,7 @@
VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse(
new VirtualMouseConfig.Builder()
.setInputDeviceName("Mouse Keys Virtual Mouse")
- .setAssociatedDisplayId(mDisplayId)
+ .setAssociatedDisplayId(displayId)
.build());
return virtualMouse;
}
@@ -375,42 +376,56 @@
if (!isMouseKey(keyCode)) {
// Pass non-mouse key events to the next handler
super.onKeyEvent(event, policyFlags);
- } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
- sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
- VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
- } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
- sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
- VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
- } else if (isDown && isMouseButtonKey(keyCode)) {
- performMouseButtonAction(keyCode);
- } else if (isDown && isMouseScrollKey(keyCode)) {
- // If the scroll key is pressed down and no other key is active,
- // set it as the active key and send a message to scroll the pointer
- if (mActiveScrollKey == KEY_NOT_SET) {
- mActiveScrollKey = keyCode;
- mLastTimeKeyActionPerformed = event.getDownTime();
- mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
- }
} else if (isDown) {
- // This is a directional key.
- // If the key is pressed down and no other key is active,
- // set it as the active key and send a message to move the pointer
- if (mActiveMoveKey == KEY_NOT_SET) {
- mActiveMoveKey = keyCode;
- mLastTimeKeyActionPerformed = event.getDownTime();
- mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+ if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCodeValue()) {
+ mScrollToggleOn = !mScrollToggleOn;
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF"));
+ }
+ } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(
+ VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_PRESS
+ );
+ } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+ sendVirtualMouseButtonEvent(
+ VirtualMouseButtonEvent.BUTTON_PRIMARY,
+ VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE
+ );
+ } else if (isMouseButtonKey(keyCode)) {
+ performMouseButtonAction(keyCode);
+ } else if (mScrollToggleOn && isMouseScrollKey(keyCode)) {
+ // If the scroll key is pressed down and no other key is active,
+ // set it as the active key and send a message to scroll the pointer
+ if (mActiveScrollKey == KEY_NOT_SET) {
+ mActiveScrollKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
+ }
+ } else {
+ // This is a directional key.
+ // If the key is pressed down and no other key is active,
+ // set it as the active key and send a message to move the pointer
+ if (mActiveMoveKey == KEY_NOT_SET) {
+ mActiveMoveKey = keyCode;
+ mLastTimeKeyActionPerformed = event.getDownTime();
+ mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+ }
}
- } else if (mActiveMoveKey == keyCode) {
- // If the key is released, and it is the active key, stop moving the pointer
- mActiveMoveKey = KEY_NOT_SET;
- mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
- } else if (mActiveScrollKey == keyCode) {
- // If the key is released, and it is the active key, stop scrolling the pointer
- mActiveScrollKey = KEY_NOT_SET;
- mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
} else {
- Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
- + "', with no matching down event from deviceId = " + event.getDeviceId());
+ // Up event received
+ if (mActiveMoveKey == keyCode) {
+ // If the key is released, and it is the active key, stop moving the pointer
+ mActiveMoveKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
+ } else if (mActiveScrollKey == keyCode) {
+ // If the key is released, and it is the active key, stop scrolling the pointer
+ mActiveScrollKey = KEY_NOT_SET;
+ mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
+ } else {
+ Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
+ + "', with no matching down event from deviceId = " + event.getDeviceId());
+ }
}
}
@@ -470,14 +485,6 @@
}
}
- @Override
- public void onInputDeviceAdded(int deviceId) {
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- }
-
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override
public void onDestroy() {
@@ -485,14 +492,8 @@
mActiveMoveKey = KEY_NOT_SET;
mActiveScrollKey = KEY_NOT_SET;
mLastTimeKeyActionPerformed = 0;
+
mHandler.removeCallbacksAndMessages(null);
-
mVirtualDevice.close();
- mInputManager.unregisterInputDeviceListener(this);
}
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
- }
-
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 2e9a4dc..a10039f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -462,7 +462,9 @@
@Override
public void onShown() {
- mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+ if (mCallback != null) {
+ mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+ }
}
@Override
@@ -511,7 +513,9 @@
@Override
public void startIntentSender(IntentSender intentSender) {
- mCallback.startIntentSenderAndFinishSession(intentSender);
+ if (mCallback != null) {
+ mCallback.startIntentSenderAndFinishSession(intentSender);
+ }
}
private void log(int type) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1cd20ed..9d4310c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -215,6 +215,7 @@
"power_hint_flags_lib",
"biometrics_flags_lib",
"am_flags_lib",
+ "updates_flags_lib",
"com_android_server_accessibility_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 3d610d3..6a6aea4 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -15,6 +15,7 @@
*/
package com.android.server;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
@@ -41,7 +42,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
@@ -363,22 +363,34 @@
@GuardedBy("mLock")
@Nullable
private ServiceInfo getServiceInfoLocked() {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
+ if (refactorCrashrecovery()) {
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ } else {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
}
- return resolveInfo.serviceInfo;
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 21947ba..95dbaae 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -1016,6 +1016,7 @@
// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the
// kernel log
doSysRq('w');
+ doSysRq('m');
doSysRq('l');
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ac9ed0d..458749d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1233,6 +1233,10 @@
obsoleteAuthType.add(type);
// And delete it from the TABLE_META
accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+ } else if (knownUid != null && knownUid != uid) {
+ Slog.w(TAG, "authenticator no longer exist for type " + type);
+ obsoleteAuthType.add(type);
+ accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
}
}
}
@@ -5048,6 +5052,9 @@
if (resolveInfo == null) {
return false;
}
+ if ("content".equals(intent.getScheme())) {
+ return false;
+ }
ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
int targetUid = targetActivityInfo.applicationInfo.uid;
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 25fb729..69ee8fc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -72,6 +72,7 @@
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.os.FactoryTest.FACTORY_TEST_OFF;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
@@ -2075,7 +2076,8 @@
app.setPersistent(true);
app.setPid(MY_PID);
app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
- app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
+ app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
+ mProcessStats);
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
updateLruProcessLocked(app, false, null);
@@ -2322,7 +2324,9 @@
} else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mService.startBroadcastObservers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mService.mPackageWatchdog.onPackagesReady();
+ if (!refactorCrashrecovery()) {
+ mService.mPackageWatchdog.onPackagesReady();
+ }
mService.scheduleHomeTimeout();
}
}
@@ -4871,7 +4875,7 @@
// Make app active after binding application or client may be running requests (e.g
// starting activities) before it is ready.
synchronized (mProcLock) {
- app.makeActive(thread, mProcessStats);
+ app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
}
app.setPendingFinishAttach(true);
diff --git a/services/core/java/com/android/server/am/ApplicationThreadDeferred.java b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
new file mode 100644
index 0000000..b0f9b53
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
@@ -0,0 +1,173 @@
+/*
+ * 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.IntDef;
+import android.app.IApplicationThread;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * A subclass of {@link IApplicationThread} that defers certain binder calls while the process is
+ * paused (frozen). Any deferred calls are executed when the process is unpaused. In some cases,
+ * multiple instances of deferred calls are collapsed into a single call when the process is
+ * unpaused.
+ *
+ * {@hide}
+ */
+class ApplicationThreadDeferred extends ApplicationThreadFilter {
+
+ static final String TAG = TAG_WITH_CLASS_NAME ? "ApplicationThreadDeferred" : TAG_AM;
+
+ // The flag that enables the deferral behavior of this class. If the flag is disabled then
+ // the class behaves exactly like an ApplicationThreadFilter.
+ private static boolean deferBindersWhenPaused() {
+ return Flags.deferBindersWhenPaused();
+ }
+
+ // The list of notifications that may be deferred.
+ private static final int CLEAR_DNS_CACHE = 0;
+ private static final int UPDATE_TIME_ZONE = 1;
+ private static final int SCHEDULE_LOW_MEMORY = 2;
+ private static final int UPDATE_HTTP_PROXY = 3;
+ private static final int NOTIFICATION_COUNT = 4;
+
+ @IntDef(value = {
+ CLEAR_DNS_CACHE,
+ UPDATE_TIME_ZONE,
+ SCHEDULE_LOW_MEMORY,
+ UPDATE_HTTP_PROXY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface NotificationType {};
+
+ private final Object mLock = new Object();
+
+ // If this is true, notifications should be queued for later delivery. If this is false,
+ // notifications should be delivered immediately.
+ @GuardedBy("mLock")
+ private boolean mPaused = false;
+
+ // An operation is a lambda that throws an exception.
+ private interface Operation {
+ void run() throws RemoteException;
+ }
+
+ // The array of operations.
+ @GuardedBy("mLock")
+ private final Operation[] mOperations = new Operation[NOTIFICATION_COUNT];
+
+ // The array of operations that actually pending right now.
+ @GuardedBy("mLock")
+ private final boolean[] mPending = new boolean[NOTIFICATION_COUNT];
+
+ // When true, binder calls to paused processes will be deferred until the process is unpaused.
+ private final boolean mDefer;
+
+ /** Create an instance with a base thread and a deferral enable flag. */
+ @VisibleForTesting
+ public ApplicationThreadDeferred(IApplicationThread thread, boolean defer) {
+ super(thread);
+
+ mDefer = defer;
+
+ mOperations[CLEAR_DNS_CACHE] = () -> { super.clearDnsCache(); };
+ mOperations[UPDATE_TIME_ZONE] = () -> { super.updateTimeZone(); };
+ mOperations[SCHEDULE_LOW_MEMORY] = () -> { super.scheduleLowMemory(); };
+ mOperations[UPDATE_HTTP_PROXY] = () -> { super.updateHttpProxy(); };
+ }
+
+ /** Create an instance with a base flag, using the system deferral enable flag. */
+ public ApplicationThreadDeferred(IApplicationThread thread) {
+ this(thread, deferBindersWhenPaused());
+ }
+
+ /** The process is being paused. Start deferring calls. */
+ void onProcessPaused() {
+ synchronized (mLock) {
+ mPaused = true;
+ }
+ }
+
+ /** The process is no longer paused. Drain any deferred calls. */
+ void onProcessUnpaused() {
+ synchronized (mLock) {
+ mPaused = false;
+ try {
+ for (int i = 0; i < mOperations.length; i++) {
+ if (mPending[i]) {
+ mOperations[i].run();
+ }
+ }
+ } catch (RemoteException e) {
+ // Swallow the exception. The caller is not expecting it. Remote exceptions
+ // happen if a has process died; there is no need to report it here.
+ } finally {
+ Arrays.fill(mPending, false);
+ }
+ }
+ }
+
+ /** The pause operation has been canceled. Drain any deferred calls. */
+ void onProcessPausedCancelled() {
+ onProcessUnpaused();
+ }
+
+ /**
+ * If the thread is not paused, execute the operation. Otherwise, save it to the pending
+ * list.
+ */
+ private void execute(@NotificationType int tag) throws RemoteException {
+ synchronized (mLock) {
+ if (mPaused && mDefer) {
+ mPending[tag] = true;
+ return;
+ }
+ }
+ // Outside the synchronization block to avoid contention.
+ mOperations[tag].run();
+ }
+
+ @Override
+ public void clearDnsCache() throws RemoteException {
+ execute(CLEAR_DNS_CACHE);
+ }
+
+ @Override
+ public void updateTimeZone() throws RemoteException {
+ execute(UPDATE_TIME_ZONE);
+ }
+
+ @Override
+ public void scheduleLowMemory() throws RemoteException {
+ execute(SCHEDULE_LOW_MEMORY);
+ }
+
+ @Override
+ public void updateHttpProxy() throws RemoteException {
+ execute(UPDATE_HTTP_PROXY);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ApplicationThreadFilter.java b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
new file mode 100644
index 0000000..d049305
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
@@ -0,0 +1,603 @@
+/*
+ * 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.am;
+
+
+class ApplicationThreadFilter implements android.app.IApplicationThread {
+ private final android.app.IApplicationThread mBase;
+ public ApplicationThreadFilter(android.app.IApplicationThread base) { mBase = base; }
+ android.app.IApplicationThread getBase() { return mBase; }
+ public android.os.IBinder asBinder() {
+ return mBase.asBinder();
+ }
+
+ @Override
+ public void scheduleReceiver(android.content.Intent intent,
+ android.content.pm.ActivityInfo info,
+ android.content.res.CompatibilityInfo compatInfo,
+ int resultCode,
+ String data,
+ android.os.Bundle extras,
+ boolean ordered,
+ boolean assumeDelivered,
+ int sendingUser,
+ int processState,
+ int sentFromUid,
+ String sentFromPackage)
+ throws android.os.RemoteException {
+ mBase.scheduleReceiver(intent,
+ info,
+ compatInfo,
+ resultCode,
+ data,
+ extras,
+ ordered,
+ assumeDelivered,
+ sendingUser,
+ processState,
+ sentFromUid,
+ sentFromPackage);
+ }
+ @Override
+ public void scheduleReceiverList(java.util.List<android.app.ReceiverInfo> info)
+ throws android.os.RemoteException {
+ mBase.scheduleReceiverList(info);
+ }
+ @Override
+ public void scheduleCreateService(android.os.IBinder token,
+ android.content.pm.ServiceInfo info,
+ android.content.res.CompatibilityInfo compatInfo,
+ int processState)
+ throws android.os.RemoteException {
+ mBase.scheduleCreateService(token,
+ info,
+ compatInfo,
+ processState);
+ }
+ @Override
+ public void scheduleStopService(android.os.IBinder token)
+ throws android.os.RemoteException {
+ mBase.scheduleStopService(token);
+ }
+ @Override
+ public void bindApplication(String packageName,
+ android.content.pm.ApplicationInfo info,
+ String sdkSandboxClientAppVolumeUuid,
+ String sdkSandboxClientAppPackage,
+ boolean isSdkInSandbox,
+ android.content.pm.ProviderInfoList providerList,
+ android.content.ComponentName testName,
+ android.app.ProfilerInfo profilerInfo,
+ android.os.Bundle testArguments,
+ android.app.IInstrumentationWatcher testWatcher,
+ android.app.IUiAutomationConnection uiAutomationConnection,
+ int debugMode,
+ boolean enableBinderTracking,
+ boolean trackAllocation,
+ boolean restrictedBackupMode,
+ boolean persistent,
+ android.content.res.Configuration config,
+ android.content.res.CompatibilityInfo compatInfo,
+ java.util.Map services,
+ android.os.Bundle coreSettings,
+ String buildSerial,
+ android.content.AutofillOptions autofillOptions,
+ android.content.ContentCaptureOptions contentCaptureOptions,
+ long[] disabledCompatChanges,
+ long[] loggableCompatChanges,
+ android.os.SharedMemory serializedSystemFontMap,
+ long startRequestedElapsedTime,
+ long startRequestedUptime)
+ throws android.os.RemoteException {
+ mBase.bindApplication(packageName,
+ info,
+ sdkSandboxClientAppVolumeUuid,
+ sdkSandboxClientAppPackage,
+ isSdkInSandbox,
+ providerList,
+ testName,
+ profilerInfo,
+ testArguments,
+ testWatcher,
+ uiAutomationConnection,
+ debugMode,
+ enableBinderTracking,
+ trackAllocation,
+ restrictedBackupMode,
+ persistent,
+ config,
+ compatInfo,
+ services,
+ coreSettings,
+ buildSerial,
+ autofillOptions,
+ contentCaptureOptions,
+ disabledCompatChanges,
+ loggableCompatChanges,
+ serializedSystemFontMap,
+ startRequestedElapsedTime,
+ startRequestedUptime);
+ }
+ @Override
+ public void runIsolatedEntryPoint(String entryPoint,
+ String[] entryPointArgs)
+ throws android.os.RemoteException {
+ mBase.runIsolatedEntryPoint(entryPoint,
+ entryPointArgs);
+ }
+ @Override
+ public void scheduleExit()
+ throws android.os.RemoteException {
+ mBase.scheduleExit();
+ }
+ @Override
+ public void scheduleServiceArgs(android.os.IBinder token,
+ android.content.pm.ParceledListSlice args)
+ throws android.os.RemoteException {
+ mBase.scheduleServiceArgs(token,
+ args);
+ }
+ @Override
+ public void updateTimeZone()
+ throws android.os.RemoteException {
+ mBase.updateTimeZone();
+ }
+ @Override
+ public void processInBackground()
+ throws android.os.RemoteException {
+ mBase.processInBackground();
+ }
+ @Override
+ public void scheduleBindService(android.os.IBinder token,
+ android.content.Intent intent,
+ boolean rebind,
+ int processState,
+ long bindSeq)
+ throws android.os.RemoteException {
+ mBase.scheduleBindService(token,
+ intent,
+ rebind,
+ processState,
+ bindSeq);
+ }
+ @Override
+ public void scheduleUnbindService(android.os.IBinder token,
+ android.content.Intent intent)
+ throws android.os.RemoteException {
+ mBase.scheduleUnbindService(token,
+ intent);
+ }
+ @Override
+ public void dumpService(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpService(fd,
+ servicetoken,
+ args);
+ }
+ @Override
+ public void scheduleRegisteredReceiver(android.content.IIntentReceiver receiver,
+ android.content.Intent intent,
+ int resultCode,
+ String data,
+ android.os.Bundle extras,
+ boolean ordered,
+ boolean sticky,
+ boolean assumeDelivered,
+ int sendingUser,
+ int processState,
+ int sentFromUid,
+ String sentFromPackage)
+ throws android.os.RemoteException {
+ mBase.scheduleRegisteredReceiver(receiver,
+ intent,
+ resultCode,
+ data,
+ extras,
+ ordered,
+ sticky,
+ assumeDelivered,
+ sendingUser,
+ processState,
+ sentFromUid,
+ sentFromPackage);
+ }
+ @Override
+ public void scheduleLowMemory()
+ throws android.os.RemoteException {
+ mBase.scheduleLowMemory();
+ }
+ @Override
+ public void profilerControl(boolean start,
+ android.app.ProfilerInfo profilerInfo,
+ int profileType)
+ throws android.os.RemoteException {
+ mBase.profilerControl(start,
+ profilerInfo,
+ profileType);
+ }
+ @Override
+ public void setSchedulingGroup(int group)
+ throws android.os.RemoteException {
+ mBase.setSchedulingGroup(group);
+ }
+ @Override
+ public void scheduleCreateBackupAgent(android.content.pm.ApplicationInfo app,
+ int backupMode,
+ int userId,
+ int operationType)
+ throws android.os.RemoteException {
+ mBase.scheduleCreateBackupAgent(app,
+ backupMode,
+ userId,
+ operationType);
+ }
+ @Override
+ public void scheduleDestroyBackupAgent(android.content.pm.ApplicationInfo app,
+ int userId)
+ throws android.os.RemoteException {
+ mBase.scheduleDestroyBackupAgent(app,
+ userId);
+ }
+ @Override
+ public void scheduleOnNewSceneTransitionInfo(android.os.IBinder token,
+ android.app.ActivityOptions.SceneTransitionInfo info)
+ throws android.os.RemoteException {
+ mBase.scheduleOnNewSceneTransitionInfo(token,
+ info);
+ }
+ @Override
+ public void scheduleSuicide()
+ throws android.os.RemoteException {
+ mBase.scheduleSuicide();
+ }
+ @Override
+ public void dispatchPackageBroadcast(int cmd,
+ String[] packages)
+ throws android.os.RemoteException {
+ mBase.dispatchPackageBroadcast(cmd,
+ packages);
+ }
+ @Override
+ public void scheduleCrash(String msg,
+ int typeId,
+ android.os.Bundle extras)
+ throws android.os.RemoteException {
+ mBase.scheduleCrash(msg,
+ typeId,
+ extras);
+ }
+ @Override
+ public void dumpHeap(boolean managed,
+ boolean mallocInfo,
+ boolean runGc,
+ String dumpBitmaps,
+ String path,
+ android.os.ParcelFileDescriptor fd,
+ android.os.RemoteCallback finishCallback)
+ throws android.os.RemoteException {
+ mBase.dumpHeap(managed,
+ mallocInfo,
+ runGc,
+ dumpBitmaps,
+ path,
+ fd,
+ finishCallback);
+ }
+ @Override
+ public void dumpActivity(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String prefix,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpActivity(fd,
+ servicetoken,
+ prefix,
+ args);
+ }
+ @Override
+ public void dumpResources(android.os.ParcelFileDescriptor fd,
+ android.os.RemoteCallback finishCallback)
+ throws android.os.RemoteException {
+ mBase.dumpResources(fd,
+ finishCallback);
+ }
+ @Override
+ public void clearDnsCache()
+ throws android.os.RemoteException {
+ mBase.clearDnsCache();
+ }
+ @Override
+ public void updateHttpProxy()
+ throws android.os.RemoteException {
+ mBase.updateHttpProxy();
+ }
+ @Override
+ public void setCoreSettings(android.os.Bundle coreSettings)
+ throws android.os.RemoteException {
+ mBase.setCoreSettings(coreSettings);
+ }
+ @Override
+ public void updatePackageCompatibilityInfo(String pkg,
+ android.content.res.CompatibilityInfo info)
+ throws android.os.RemoteException {
+ mBase.updatePackageCompatibilityInfo(pkg,
+ info);
+ }
+ @Override
+ public void scheduleTrimMemory(int level)
+ throws android.os.RemoteException {
+ mBase.scheduleTrimMemory(level);
+ }
+ @Override
+ public void dumpMemInfo(android.os.ParcelFileDescriptor fd,
+ android.os.Debug.MemoryInfo mem,
+ boolean checkin,
+ boolean dumpInfo,
+ boolean dumpDalvik,
+ boolean dumpSummaryOnly,
+ boolean dumpUnreachable,
+ boolean dumpAllocatorLogs,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpMemInfo(fd,
+ mem,
+ checkin,
+ dumpInfo,
+ dumpDalvik,
+ dumpSummaryOnly,
+ dumpUnreachable,
+ dumpAllocatorLogs,
+ args);
+ }
+ @Override
+ public void dumpMemInfoProto(android.os.ParcelFileDescriptor fd,
+ android.os.Debug.MemoryInfo mem,
+ boolean dumpInfo,
+ boolean dumpDalvik,
+ boolean dumpSummaryOnly,
+ boolean dumpUnreachable,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpMemInfoProto(fd,
+ mem,
+ dumpInfo,
+ dumpDalvik,
+ dumpSummaryOnly,
+ dumpUnreachable,
+ args);
+ }
+ @Override
+ public void dumpGfxInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpGfxInfo(fd,
+ args);
+ }
+ @Override
+ public void dumpCacheInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpCacheInfo(fd,
+ args);
+ }
+ @Override
+ public void dumpProvider(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpProvider(fd,
+ servicetoken,
+ args);
+ }
+ @Override
+ public void dumpDbInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpDbInfo(fd,
+ args);
+ }
+ @Override
+ public void unstableProviderDied(android.os.IBinder provider)
+ throws android.os.RemoteException {
+ mBase.unstableProviderDied(provider);
+ }
+ @Override
+ public void requestAssistContextExtras(android.os.IBinder activityToken,
+ android.os.IBinder requestToken,
+ int requestType,
+ int sessionId,
+ int flags)
+ throws android.os.RemoteException {
+ mBase.requestAssistContextExtras(activityToken,
+ requestToken,
+ requestType,
+ sessionId,
+ flags);
+ }
+ @Override
+ public void scheduleTranslucentConversionComplete(android.os.IBinder token,
+ boolean timeout)
+ throws android.os.RemoteException {
+ mBase.scheduleTranslucentConversionComplete(token,
+ timeout);
+ }
+ @Override
+ public void setProcessState(int state)
+ throws android.os.RemoteException {
+ mBase.setProcessState(state);
+ }
+ @Override
+ public void scheduleInstallProvider(android.content.pm.ProviderInfo provider)
+ throws android.os.RemoteException {
+ mBase.scheduleInstallProvider(provider);
+ }
+ @Override
+ public void updateTimePrefs(int timeFormatPreference)
+ throws android.os.RemoteException {
+ mBase.updateTimePrefs(timeFormatPreference);
+ }
+ @Override
+ public void scheduleEnterAnimationComplete(android.os.IBinder token)
+ throws android.os.RemoteException {
+ mBase.scheduleEnterAnimationComplete(token);
+ }
+ @Override
+ public void notifyCleartextNetwork(byte[] firstPacket)
+ throws android.os.RemoteException {
+ mBase.notifyCleartextNetwork(firstPacket);
+ }
+ @Override
+ public void startBinderTracking()
+ throws android.os.RemoteException {
+ mBase.startBinderTracking();
+ }
+ @Override
+ public void stopBinderTrackingAndDump(android.os.ParcelFileDescriptor fd)
+ throws android.os.RemoteException {
+ mBase.stopBinderTrackingAndDump(fd);
+ }
+ @Override
+ public void scheduleLocalVoiceInteractionStarted(android.os.IBinder token,
+ com.android.internal.app.IVoiceInteractor voiceInteractor)
+ throws android.os.RemoteException {
+ mBase.scheduleLocalVoiceInteractionStarted(token,
+ voiceInteractor);
+ }
+ @Override
+ public void handleTrustStorageUpdate()
+ throws android.os.RemoteException {
+ mBase.handleTrustStorageUpdate();
+ }
+ @Override
+ public void attachAgent(String path)
+ throws android.os.RemoteException {
+ mBase.attachAgent(path);
+ }
+ @Override
+ public void attachStartupAgents(String dataDir)
+ throws android.os.RemoteException {
+ mBase.attachStartupAgents(dataDir);
+ }
+ @Override
+ public void scheduleApplicationInfoChanged(android.content.pm.ApplicationInfo ai)
+ throws android.os.RemoteException {
+ mBase.scheduleApplicationInfoChanged(ai);
+ }
+ @Override
+ public void setNetworkBlockSeq(long procStateSeq)
+ throws android.os.RemoteException {
+ mBase.setNetworkBlockSeq(procStateSeq);
+ }
+ @Override
+ public void scheduleTransaction(android.app.servertransaction.ClientTransaction transaction)
+ throws android.os.RemoteException {
+ mBase.scheduleTransaction(transaction);
+ }
+ @Override
+ public void scheduleTaskFragmentTransaction(android.window.ITaskFragmentOrganizer organizer,
+ android.window.TaskFragmentTransaction transaction)
+ throws android.os.RemoteException {
+ mBase.scheduleTaskFragmentTransaction(organizer,
+ transaction);
+ }
+ @Override
+ public void requestDirectActions(android.os.IBinder activityToken,
+ com.android.internal.app.IVoiceInteractor intractor,
+ android.os.RemoteCallback cancellationCallback,
+ android.os.RemoteCallback callback)
+ throws android.os.RemoteException {
+ mBase.requestDirectActions(activityToken,
+ intractor,
+ cancellationCallback,
+ callback);
+ }
+ @Override
+ public void performDirectAction(android.os.IBinder activityToken,
+ String actionId,
+ android.os.Bundle arguments,
+ android.os.RemoteCallback cancellationCallback,
+ android.os.RemoteCallback resultCallback)
+ throws android.os.RemoteException {
+ mBase.performDirectAction(activityToken,
+ actionId,
+ arguments,
+ cancellationCallback,
+ resultCallback);
+ }
+ @Override
+ public void notifyContentProviderPublishStatus(android.app.ContentProviderHolder holder,
+ String authorities,
+ int userId,
+ boolean published)
+ throws android.os.RemoteException {
+ mBase.notifyContentProviderPublishStatus(holder,
+ authorities,
+ userId,
+ published);
+ }
+ @Override
+ public void instrumentWithoutRestart(android.content.ComponentName instrumentationName,
+ android.os.Bundle instrumentationArgs,
+ android.app.IInstrumentationWatcher instrumentationWatcher,
+ android.app.IUiAutomationConnection instrumentationUiConnection,
+ android.content.pm.ApplicationInfo targetInfo)
+ throws android.os.RemoteException {
+ mBase.instrumentWithoutRestart(instrumentationName,
+ instrumentationArgs,
+ instrumentationWatcher,
+ instrumentationUiConnection,
+ targetInfo);
+ }
+ @Override
+ public void updateUiTranslationState(android.os.IBinder activityToken,
+ int state,
+ android.view.translation.TranslationSpec sourceSpec,
+ android.view.translation.TranslationSpec targetSpec,
+ java.util.List<android.view.autofill.AutofillId> viewIds,
+ android.view.translation.UiTranslationSpec uiTranslationSpec)
+ throws android.os.RemoteException {
+ mBase.updateUiTranslationState(activityToken,
+ state,
+ sourceSpec,
+ targetSpec,
+ viewIds,
+ uiTranslationSpec);
+ }
+ @Override
+ public void scheduleTimeoutService(android.os.IBinder token,
+ int startId)
+ throws android.os.RemoteException {
+ mBase.scheduleTimeoutService(token,
+ startId);
+ }
+ @Override
+ public void scheduleTimeoutServiceForType(android.os.IBinder token,
+ int startId,
+ int fgsType)
+ throws android.os.RemoteException {
+ mBase.scheduleTimeoutServiceForType(token,
+ startId,
+ fgsType);
+ }
+ @Override
+ public void schedulePing(android.os.RemoteCallback pong)
+ throws android.os.RemoteException {
+ mBase.schedulePing(pong);
+ }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 00183ac..67985ef 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -76,6 +76,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.os.WakeLockStats;
import android.os.WorkSource;
@@ -158,6 +159,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1107,6 +1109,13 @@
FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR, pullAtomCallback);
+ if (Flags.addBatteryUsageStatsSliceAtom()) {
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ pullAtomCallback);
+ }
}
/** StatsPullAtomCallback for pulling BatteryUsageStats data. */
@@ -1115,7 +1124,7 @@
public int onPullAtom(int atomTag, List<StatsEvent> data) {
final BatteryUsageStats bus;
switch (atomTag) {
- case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
@SuppressLint("MissingPermission")
final double minConsumedPowerThreshold =
DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1130,6 +1139,7 @@
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
+ }
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
@@ -1141,7 +1151,7 @@
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
- case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
final long sessionStart =
getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
@@ -1158,6 +1168,31 @@
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
+ }
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID: {
+ if (!Flags.addBatteryUsageStatsSliceAtom()) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ @SuppressLint("MissingPermission")
+ final double minConsumedPowerThreshold =
+ DeviceConfig.getFloat(
+ DEVICE_CONFIG_NAMESPACE,
+ MIN_CONSUMED_POWER_THRESHOLD_KEY,
+ 0);
+ final long sessionStart = 0;
+ final long sessionEnd = System.currentTimeMillis();
+ final BatteryUsageStatsQuery query =
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .aggregateSnapshots(sessionStart, sessionEnd)
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
+ .build();
+ bus = getBatteryUsageStats(List.of(query)).get(0);
+ return StatsPerUidLogger.logStats(bus, data);
+ }
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -1169,6 +1204,262 @@
}
}
+ private static class StatsPerUidLogger {
+
+ private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
+
+ private static final int[] UID_PROCESS_STATES = {
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED
+ };
+
+ public record SessionInfo(
+ long startTs,
+ long endTs,
+ long duration,
+ int dischargePercentage,
+ long dischargeDuration) {}
+ ;
+
+ static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+ final SessionInfo sessionInfo =
+ new SessionInfo(
+ bus.getStatsStartTimestamp(),
+ bus.getStatsEndTimestamp(),
+ bus.getStatsDuration(),
+ bus.getDischargePercentage(),
+ bus.getDischargeDurationMs());
+
+ if (DBG) {
+ Slog.d(TAG, "BatteryUsageStats dump = " + bus);
+ }
+ final BatteryConsumer deviceConsumer =
+ bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ processState,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+
+ final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
+ uidConsumers.sort(
+ Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+ .reversed());
+
+ // Log single atom for BatteryUsageStats per uid/process_state/component/etc.
+ for (UidBatteryConsumer uidConsumer : uidConsumers) {
+ final int uid = uidConsumer.getUid();
+ final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ // looping over custom components
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+ final long timeInStateMillis =
+ uidConsumer.getTimeInProcessStateMs(processState);
+ if (timeInStateMillis <= 0) {
+ continue;
+ }
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private static boolean addStatsForPredefinedComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ @BatteryConsumer.PowerComponent int componentId) {
+ final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
+ if (key == null) {
+ return true;
+ }
+
+ final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+ final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && powerComponentDurationMillis == 0) {
+ return true;
+ }
+
+ long timeInState = 0;
+ if (batteryConsumer instanceof UidBatteryConsumer) {
+ timeInState =
+ ((UidBatteryConsumer) batteryConsumer)
+ .getTimeInProcessStateMs(processState);
+ }
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInState,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ private static boolean addStatsForCustomComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ long timeInStateMillis,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ int componentId) {
+
+ if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ throw new IllegalArgumentException("Invalid custom component id: " + componentId);
+ }
+
+ final float powerMah =
+ (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
+ if (powerMah == 0) {
+ return true;
+ }
+
+ final String powerComponentName =
+ batteryConsumer.getCustomPowerComponentName(componentId);
+
+ final long powerComponentDurationMillis =
+ batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ /**
+ * Returns true on success and false if reached max atoms capacity and no more atoms should
+ * be added
+ */
+ private static boolean addStatsAtom(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ int processState,
+ long timeInStateMillis,
+ String powerComponentName,
+ float totalConsumedPowerMah,
+ float powerComponentMah,
+ long powerComponentDurationMillis) {
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ sessionInfo.startTs(),
+ sessionInfo.endTs(),
+ sessionInfo.duration(),
+ sessionInfo.dischargePercentage(),
+ sessionInfo.dischargeDuration(),
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerComponentMah,
+ powerComponentDurationMillis));
+
+ // Early termination due to statsd dimensions guardrail
+ if (data.size() == STATSD_METRIC_MAX_DIMENSIONS_COUNT) {
+ Slog.w(
+ TAG,
+ "BATTERY_USAGE_STATS_PER_UID is complete reaching"
+ + " dimension guardrail");
+ return false;
+ }
+ return true;
+ }
+ }
+
@Override
@RequiresNoPermission
public boolean isCharging() {
@@ -2824,9 +3115,11 @@
pw.println(" --checkin: generate output for a checkin report; will write (and clear) the");
pw.println(" last old completed stats when they had been reset.");
pw.println(" -c: write the current stats in checkin format.");
- pw.println(" --proto: write the current aggregate stats (without history) in proto format.");
+ pw.println(
+ " --proto: write the current aggregate stats (without history) in proto format.");
pw.println(" --history: show only history data.");
- pw.println(" --history-start <num>: show only history data starting at given time offset.");
+ pw.println(
+ " --history-start <num>: show only history data starting at given time offset.");
pw.println(" --history-create-events <num>: create <num> of battery history events.");
pw.println(" --charged: only output data since last charged.");
pw.println(" --daily: only output full daily data.");
@@ -2850,12 +3143,15 @@
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
pw.println(" enable|disable <option>");
- pw.println(" Enable or disable a running option. Option state is not saved across boots.");
+ pw.println(
+ " Enable or disable a running option. Option state is not saved across boots.");
pw.println(" Options are:");
pw.println(" full-history: include additional detailed events in battery history:");
pw.println(" wake_lock_in, alarms and proc events");
pw.println(" no-auto-reset: don't automatically reset stats when unplugged");
- pw.println(" pretend-screen-off: pretend the screen is off, even if screen state changes");
+ pw.println(
+ " pretend-screen-off: pretend the screen is off, even if screen state"
+ + " changes");
}
private void dumpSettings(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6433f2c..1c4ffbb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -2656,6 +2656,7 @@
// PIDs that run out of async binder buffer when being frozen
ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorSync");
for (int i = 0; i < pids.size(); i++) {
int current = pids.get(i);
try {
@@ -2684,6 +2685,7 @@
Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
}
}
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// TODO: when kernel binder driver supports, poll the binder status directly.
// Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
@@ -2693,6 +2695,8 @@
if (pidsAsync == null || pidsAsync.size() == 0) {
return;
}
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorAsync");
new BinderfsStatsReader().handleFreeAsyncSpace(
// Check if the frozen process has pending async calls
pidsAsync::contains,
@@ -2710,5 +2714,6 @@
// Log the error if binderfs stats can't be accesses or correctly parsed
exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a74c489..3e71d00 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -151,7 +151,7 @@
* (in which case we are in the process of launching the app).
*/
@CompositeRWLock({"mService", "mProcLock"})
- private IApplicationThread mThread;
+ private ApplicationThreadDeferred mThread;
/**
* Instance of {@link #mThread} that will always meet the {@code oneway}
@@ -737,15 +737,15 @@
}
@GuardedBy({"mService", "mProcLock"})
- public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
+ public void makeActive(ApplicationThreadDeferred thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
mThread = thread;
if (mPid == Process.myPid()) {
- mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
+ mOnewayThread = new SameProcessApplicationThread(mThread, FgThread.getHandler());
} else {
- mOnewayThread = thread;
+ mOnewayThread = mThread;
}
- mWindowProcessController.setThread(thread);
+ mWindowProcessController.setThread(mThread);
if (mWindowProcessController.useFifoUiScheduling()) {
mService.mSpecifiedFifoProcesses.add(this);
}
@@ -1436,14 +1436,17 @@
void onProcessFrozen() {
mProfile.onProcessFrozen();
+ if (mThread != null) mThread.onProcessPaused();
}
void onProcessUnfrozen() {
+ if (mThread != null) mThread.onProcessUnpaused();
mProfile.onProcessUnfrozen();
mServices.onProcessUnfrozen();
}
void onProcessFrozenCancelled() {
+ if (mThread != null) mThread.onProcessPausedCancelled();
mServices.onProcessFrozenCancelled();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 932b9c0..7f43fae 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -210,6 +210,7 @@
"safety_center",
"sensors",
"spoon",
+ "stability",
"statsd",
"system_performance",
"system_sw_touch",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 9b380ff..5315167 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -163,7 +163,7 @@
flag {
name: "collect_logcat_on_run_synchronously"
- namespace: "dropbox"
+ namespace: "stability"
description: "Allow logcat collection on synchronous dropbox collection"
bug: "324222683"
is_fixed_read_only: true
@@ -171,8 +171,16 @@
flag {
name: "enable_dropbox_watchdog_headers"
- namespace: "dropbox"
+ namespace: "stability"
description: "Add watchdog-specific dropbox headers"
bug: "330682397"
is_fixed_read_only: true
}
+
+flag {
+ name: "defer_binders_when_paused"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Defer submitting binder calls to paused processes."
+ bug: "327038797"
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d309eb..a84306b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,6 +739,8 @@
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
+ private final Executor mAudioServerLifecycleExecutor;
+
private IMediaProjectionManager mProjectionService; // to validate projection token
/** Interface for UserManagerService. */
@@ -1059,7 +1061,8 @@
audioserverPermissions() ?
initializeAudioServerPermissionProvider(
context, audioPolicyFacade, audioserverLifecycleExecutor) :
- null
+ null,
+ audioserverLifecycleExecutor
);
}
@@ -1145,13 +1148,16 @@
* {@link AudioSystemThread} is created as the messaging thread instead.
* @param appOps {@link AppOpsManager} system service
* @param enforcer Used for permission enforcing
+ * @param permissionProvider Used to push permissions to audioserver
+ * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
- /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider,
+ Executor audioserverLifecycleExecutor) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
@@ -1159,6 +1165,7 @@
mAppOps = appOps;
mPermissionProvider = permissionProvider;
+ mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -1170,6 +1177,34 @@
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
+ // Listen to permission invalidations for the PermissionProvider
+ if (audioserverPermissions()) {
+ final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
+ mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ new Runnable() {
+ // Roughly chosen to be long enough to suppress the autocork behavior
+ // of the permission cache (50ms), and longer than the task could reasonably
+ // take, even with many packages and users, while not introducing visible
+ // permission leaks - since the app needs to restart, and trigger an action
+ // which requires permissions from audioserver before this delay.
+ // For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 110;
+ final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
+ @Override
+ public void run() {
+ var currentTime = SystemClock.uptimeMillis();
+ if (currentTime > scheduledUpdateTimestamp.get()) {
+ scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
+ broadcastHandler.postAtTime( () ->
+ mAudioServerLifecycleExecutor.execute(mPermissionProvider
+ ::onPermissionStateChanged),
+ currentTime + UPDATE_DELAY_MS
+ );
+ }
+ }
+ });
+ }
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -11965,29 +12000,6 @@
provider.onServiceStart(audioPolicy.getPermissionController());
});
- // Set up event listeners
- // Must be kept in sync with PermissionManager
- Runnable cacheSysPropHandler = new Runnable() {
- private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
- private AtomicLong mNonce = new AtomicLong();
- @Override
- public void run() {
- if (mHandle.get() == null) {
- // Cache the handle
- mHandle.compareAndSet(null, SystemProperties.find(
- PermissionManager.CACHE_KEY_PACKAGE_INFO));
- }
- long nonce;
- SystemProperties.Handle ref;
- if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
- mNonce.getAndSet(nonce) != nonce) {
- audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
- }
- }
- };
-
- SystemProperties.addChangeCallback(cacheSysPropHandler);
-
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7f4bc74..d083c68 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,6 +748,10 @@
return AudioSystem.setMasterMute(mute);
}
+ public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+ AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ }
+
/**
* Part of AudioService dump
* @param pw
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0de3428..57bffa7 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -470,8 +470,13 @@
if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
return false;
}
- return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+ try {
+ return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
+ }
+ return false;
}
/*package*/ synchronized boolean isBluetoothScoRequestedInternally() {
@@ -1150,12 +1155,16 @@
}
private void checkScoAudioState() {
- if (mBluetoothHeadset != null
- && mBluetoothHeadsetDevice != null
- && mScoAudioState == SCO_STATE_INACTIVE
- && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ try {
+ if (mBluetoothHeadset != null
+ && mBluetoothHeadsetDevice != null
+ && mScoAudioState == SCO_STATE_INACTIVE
+ && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
}
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
new file mode 100644
index 0000000..317c91e
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import android.content.Context;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.RescueParty;
+import com.android.server.SystemService;
+
+
+/** This class encapsulate the lifecycle methods of CrashRecovery module. */
+public class CrashRecoveryModule {
+ private static final String TAG = "CrashRecoveryModule";
+
+ /** Lifecycle definition for CrashRecovery module. */
+ public static class Lifecycle extends SystemService {
+ private Context mSystemContext;
+ private PackageWatchdog mPackageWatchdog;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mSystemContext = context;
+ mPackageWatchdog = PackageWatchdog.getInstance(context);
+ }
+
+ @Override
+ public void onStart() {
+ RescueParty.registerHealthObserver(mSystemContext);
+ mPackageWatchdog.noteBoot();
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mPackageWatchdog.onPackagesReady();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
index 4a66bac..615db34 100644
--- a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -7,6 +7,9 @@
"include-filter": "com.android.server.RescuePartyTest"
}
]
+ },
+ {
+ "name": "CrashRecoveryModuleTests"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 9b37418..515e704 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.clamper.HdrClamper;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -157,7 +158,7 @@
private void updateHdrClamper(DisplayDeviceInfo info, IBinder token,
DisplayDeviceConfig displayDeviceConfig) {
if (mUseHdrClamper) {
- DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ HighBrightnessModeData hbmData =
displayDeviceConfig.getHighBrightnessModeData();
float minimumHdrPercentOfScreen =
hbmData == null ? -1f : hbmData.minimumHdrPercentOfScreen;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e4db634..7a055d1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -53,9 +53,9 @@
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.EvenDimmerBrightnessData;
-import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeout;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
@@ -75,8 +75,6 @@
import com.android.server.display.config.RefreshRateThrottlingMap;
import com.android.server.display.config.RefreshRateThrottlingPoint;
import com.android.server.display.config.RefreshRateZone;
-import com.android.server.display.config.SdrHdrRatioMap;
-import com.android.server.display.config.SdrHdrRatioPoint;
import com.android.server.display.config.SensorData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
@@ -302,6 +300,19 @@
* <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
* <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
* <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ * <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
+ * <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
+ * <allowInLowPowerMode>true</allowInLowPowerMode>
+ * <sdrHdrRatioMap>
+ * <point>
+ * <first>2.0</first>
+ * <second>4.0</second>
+ * </point>
+ * <point>
+ * <first>100</first>
+ * <second>8.0</second>
+ * </point>
+ * </sdrHdrRatioMap>
* </hdrBrightnessConfig>
* <luxThrottling>
* <brightnessLimitMap>
@@ -608,6 +619,15 @@
* </idleScreenRefreshRateTimeout>
* <supportsVrr>true</supportsVrr>
*
+ * <dozeBrightnessSensorValueToBrightness>
+ * <item>-1</item> <!-- 0: OFF -->
+ * <item>0.003937008</item> <!-- 1: NIGHT -->
+ * <item>0.015748031</item> <!-- 2: LOW -->
+ * <item>0.102362205</item> <!-- 3: HIGH -->
+ * <item>0.106299213</item> <!-- 4: SUN -->
+ * </dozeBrightnessSensorValueToBrightness>
+ * <defaultDozeBrightness>0.235</defaultDozeBrightness>
+ *
* </displayConfiguration>
* }
* </pre>
@@ -627,6 +647,10 @@
public static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
+ // so -2 is used instead
+ public static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
+
@VisibleForTesting
static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -645,10 +669,6 @@
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
- // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
- // so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -662,6 +682,8 @@
@VisibleForTesting
static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+ private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -743,13 +765,13 @@
private Spline mNitsToBacklightSpline;
private List<String> mQuirks;
- private boolean mIsHighBrightnessModeEnabled = false;
+ @Nullable
private HighBrightnessModeData mHbmData;
@Nullable
private PowerThrottlingConfigData mPowerThrottlingConfigData;
private DensityMapping mDensityMapping;
private String mLoadedFrom = null;
- private Spline mSdrToHdrRatioSpline;
+
// Represents the auto-brightness brightening light debounce.
private long mAutoBrightnessBrighteningLightDebounce =
@@ -869,10 +891,14 @@
private boolean mVrrSupportEnabled;
+ @Nullable
+ private float[] mDozeBrightnessSensorValueToBrightness;
+ private float mDefaultDozeBrightness;
+
private final DisplayManagerFlags mFlags;
@VisibleForTesting
- DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
+ public DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
mContext = context;
mFlags = flags;
}
@@ -1155,7 +1181,7 @@
* @return true if there is sdrHdrRatioMap, false otherwise.
*/
public boolean hasSdrToHdrRatioSpline() {
- return mSdrToHdrRatioSpline != null;
+ return mHbmData != null && mHbmData.sdrToHdrRatioSpline != null;
}
/**
@@ -1165,7 +1191,8 @@
* @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
*/
public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) {
- if (mSdrToHdrRatioSpline == null) {
+ Spline sdrToHdrSpline = mHbmData != null ? mHbmData.sdrToHdrRatioSpline : null;
+ if (sdrToHdrSpline == null) {
return PowerManager.BRIGHTNESS_INVALID;
}
@@ -1175,7 +1202,7 @@
return PowerManager.BRIGHTNESS_INVALID;
}
- float ratio = Math.min(mSdrToHdrRatioSpline.interpolate(nits), maxDesiredHdrSdrRatio);
+ float ratio = Math.min(sdrToHdrSpline.interpolate(nits), maxDesiredHdrSdrRatio);
float hdrNits = nits * ratio;
if (getNitsToBacklightSpline() == null) {
return PowerManager.BRIGHTNESS_INVALID;
@@ -1321,13 +1348,11 @@
* @return high brightness mode configuration data for the display.
*/
public HighBrightnessModeData getHighBrightnessModeData() {
- if (!mIsHighBrightnessModeEnabled || mHbmData == null) {
+ if (mHbmData == null || !mHbmData.isHighBrightnessModeEnabled) {
return null;
}
- HighBrightnessModeData hbmData = new HighBrightnessModeData();
- mHbmData.copyTo(hbmData);
- return hbmData;
+ return mHbmData;
}
/**
@@ -1585,6 +1610,24 @@
return mVrrSupportEnabled;
}
+ /**
+ * While the device is dozing, a designated light sensor is used to determine the brightness.
+ * @return The mapping between doze brightness sensor values and brightness values. The value
+ * -1 means that the current brightness should be kept.
+ */
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness() {
+ return mDozeBrightnessSensorValueToBrightness;
+ }
+
+ /**
+ * @return The default doze brightness to use while no other doze brightness is available. Can
+ * be {@link PowerManager#BRIGHTNESS_INVALID_FLOAT} if undefined.
+ */
+ public float getDefaultDozeBrightness() {
+ return mDefaultDozeBrightness;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1604,11 +1647,10 @@
+ ", mBacklightMaximum=" + mBacklightMaximum
+ ", mBrightnessDefault=" + mBrightnessDefault
+ ", mQuirks=" + mQuirks
- + ", mIsHighBrightnessModeEnabled=" + mIsHighBrightnessModeEnabled
+ "\n"
+ "mLuxThrottlingData=" + mLuxThrottlingData
+ ", mHbmData=" + mHbmData
- + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+
+ ", mThermalBrightnessThrottlingDataMapByThrottlingId="
+ mThermalBrightnessThrottlingDataMapByThrottlingId
+ "\n"
@@ -1683,6 +1725,9 @@
? mEvenDimmerBrightnessData.toString() : "null")
+ "\n"
+ "mVrrSupported= " + mVrrSupportEnabled + "\n"
+ + "mDozeBrightnessSensorValueToBrightness= "
+ + Arrays.toString(mDozeBrightnessSensorValueToBrightness) + "\n"
+ + "mDefaultDozeBrightness= " + mDefaultDozeBrightness + "\n"
+ "}";
}
@@ -1715,7 +1760,7 @@
}
@VisibleForTesting
- boolean initFromFile(File configFile) {
+ public boolean initFromFile(File configFile) {
if (!configFile.exists()) {
// Display configuration files aren't required to exist.
return false;
@@ -1740,7 +1785,23 @@
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
loadPowerThrottlingConfigData(config);
- loadHighBrightnessModeData(config);
+ // Backlight and evenDimmer data should be loaded for HbmData
+ mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, (hbm) -> {
+ float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
+ if (transitionPointBacklightScale >= mBacklightMaximum) {
+ throw new IllegalArgumentException("HBM transition point invalid. "
+ + mHbmData.transitionPoint + " is not less than "
+ + mBacklightMaximum);
+ }
+ return getBrightnessFromBacklight(transitionPointBacklightScale);
+ });
+ if (mHbmData.isHighBrightnessModeEnabled && mHbmData.refreshRateLimit != null) {
+ // TODO(b/331650248): cleanup, DMD can use mHbmData.refreshRateLimit
+ mRefreshRateLimitations.add(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+ mHbmData.refreshRateLimit));
+ }
+
loadLuxThrottling(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -1761,6 +1822,7 @@
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
mVrrSupportEnabled = config.getSupportsVrr();
+ loadDozeBrightness(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1789,6 +1851,7 @@
loadRefreshRateSetting(null);
loadBrightnessCapForWearBedtimeModeFromConfigXml();
loadIdleScreenRefreshRateTimeoutConfigs(null);
+ loadDozeBrightness(null);
mLoadedFrom = "<config.xml>";
}
@@ -1938,40 +2001,6 @@
constrainNitsAndBacklightArrays();
}
- private Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) {
- final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all();
-
- if (sdrHdrRatioMap == null) {
- return null;
- }
-
- final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint();
- final int size = points.size();
- if (size == 0) {
- return null;
- }
-
- float[] nits = new float[size];
- float[] ratios = new float[size];
-
- int i = 0;
- for (SdrHdrRatioPoint point : points) {
- nits[i] = point.getSdrNits().floatValue();
- if (i > 0) {
- if (nits[i] < nits[i - 1]) {
- Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest "
- + " of configuration. nits: " + nits[i] + " < "
- + nits[i - 1]);
- return null;
- }
- }
- ratios[i] = point.getHdrRatio().floatValue();
- ++i;
- }
-
- return Spline.createSpline(nits, ratios);
- }
-
private void loadThermalThrottlingConfig(DisplayConfiguration config) {
final ThermalThrottling throttlingConfig = config.getThermalThrottling();
if (throttlingConfig == null) {
@@ -2525,49 +2554,6 @@
}
}
- private void loadHighBrightnessModeData(DisplayConfiguration config) {
- final HighBrightnessMode hbm = config.getHighBrightnessMode();
- if (hbm != null) {
- mIsHighBrightnessModeEnabled = hbm.getEnabled();
- mHbmData = new HighBrightnessModeData();
- mHbmData.minimumLux = hbm.getMinimumLux_all().floatValue();
- float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
- if (transitionPointBacklightScale >= mBacklightMaximum) {
- throw new IllegalArgumentException("HBM transition point invalid. "
- + mHbmData.transitionPoint + " is not less than "
- + mBacklightMaximum);
- }
- mHbmData.transitionPoint =
- getBrightnessFromBacklight(transitionPointBacklightScale);
- final HbmTiming hbmTiming = hbm.getTiming_all();
- mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
- mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
- mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
- mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
- final RefreshRateRange rr = hbm.getRefreshRate_all();
- if (rr != null) {
- final float min = rr.getMinimum().floatValue();
- final float max = rr.getMaximum().floatValue();
- mRefreshRateLimitations.add(new RefreshRateLimitation(
- DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max));
- }
- BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all();
- if (minHdrPctOfScreen != null) {
- mHbmData.minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue();
- if (mHbmData.minimumHdrPercentOfScreen > 1
- || mHbmData.minimumHdrPercentOfScreen < 0) {
- Slog.w(TAG, "Invalid minimum HDR percent of screen: "
- + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
- mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
- }
- } else {
- mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
- }
-
- mSdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm);
- }
- }
-
private void loadLuxThrottling(DisplayConfiguration config) {
LuxThrottling cfg = config.getLuxThrottling();
if (cfg != null) {
@@ -2800,6 +2786,37 @@
}
}
+ private void loadDozeBrightness(DisplayConfiguration config) {
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDozeBrightnessSensorValueToBrightness() != null) {
+ List<BigDecimal> values = config.getDozeBrightnessSensorValueToBrightness().getItem();
+ mDozeBrightnessSensorValueToBrightness = new float[values.size()];
+ for (int i = 0; i < values.size(); i++) {
+ float backlight = values.get(i).floatValue();
+ if (backlight != KEEP_CURRENT_BRIGHTNESS) {
+ mDozeBrightnessSensorValueToBrightness[i] =
+ getBrightnessFromBacklight(backlight);
+ } else {
+ mDozeBrightnessSensorValueToBrightness[i] = KEEP_CURRENT_BRIGHTNESS;
+ }
+ }
+ }
+
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDefaultDozeBrightness() != null) {
+ float backlight = config.getDefaultDozeBrightness().floatValue();
+ mDefaultDozeBrightness = getBrightnessFromBacklight(backlight);
+ } else {
+ mDefaultDozeBrightness = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessDozeFloat);
+ if (mDefaultDozeBrightness == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mDefaultDozeBrightness = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze));
+ }
+ }
+ }
+
private void validateIdleScreenRefreshRateTimeoutConfig(
IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
@@ -2921,73 +2938,6 @@
}
/**
- * Container for high brightness mode configuration data.
- */
- static class HighBrightnessModeData {
- /** Minimum lux needed to enter high brightness mode */
- public float minimumLux;
-
- /** Brightness level at which we transition from normal to high-brightness. */
- public float transitionPoint;
-
- /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
- public boolean allowInLowPowerMode;
-
- /** Time window for HBM. */
- public long timeWindowMillis;
-
- /** Maximum time HBM is allowed to be during in a {@code timeWindowMillis}. */
- public long timeMaxMillis;
-
- /** Minimum time that HBM can be on before being enabled. */
- public long timeMinMillis;
-
- /** Minimum HDR video size to enter high brightness mode */
- public float minimumHdrPercentOfScreen;
-
- HighBrightnessModeData() {}
-
- HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
- long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
- float minimumHdrPercentOfScreen) {
- this.minimumLux = minimumLux;
- this.transitionPoint = transitionPoint;
- this.timeWindowMillis = timeWindowMillis;
- this.timeMaxMillis = timeMaxMillis;
- this.timeMinMillis = timeMinMillis;
- this.allowInLowPowerMode = allowInLowPowerMode;
- this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
- }
-
- /**
- * Copies the HBM data to the specified parameter instance.
- * @param other the instance to copy data to.
- */
- public void copyTo(@NonNull HighBrightnessModeData other) {
- other.minimumLux = minimumLux;
- other.timeWindowMillis = timeWindowMillis;
- other.timeMaxMillis = timeMaxMillis;
- other.timeMinMillis = timeMinMillis;
- other.transitionPoint = transitionPoint;
- other.allowInLowPowerMode = allowInLowPowerMode;
- other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
- }
-
- @Override
- public String toString() {
- return "HBM{"
- + "minLux: " + minimumLux
- + ", transition: " + transitionPoint
- + ", timeWindow: " + timeWindowMillis + "ms"
- + ", timeMax: " + timeMaxMillis + "ms"
- + ", timeMin: " + timeMinMillis + "ms"
- + ", allowInLowPowerMode: " + allowInLowPowerMode
- + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
- + "} ";
- }
- }
-
- /**
* Container for Power throttling configuration data.
* TODO(b/302814899): extract to separate class.
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3493381b..b3a6c1c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4726,6 +4726,32 @@
DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
token, displayId, modeIds);
}
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ getDozeBrightnessSensorValueToBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config: " + displayId);
+ }
+ return ddc.getDozeBrightnessSensorValueToBrightness();
+ }
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float getDefaultDozeBrightness(int displayId) {
+ getDefaultDozeBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config for doze-default: " + displayId);
+ }
+ return ddc.getDefaultDozeBrightness();
+ }
}
private static boolean isValidBrightness(float brightness) {
@@ -5240,7 +5266,7 @@
mHandler.sendMessage(msg);
mLogicalDisplayMapper
- .setDeviceStateLocked(deviceState.getIdentifier());
+ .setDeviceStateLocked(deviceState);
}
}
};
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 76a561b..5c1e783 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -87,6 +87,7 @@
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
@@ -271,7 +272,7 @@
private final SettingsObserver mSettingsObserver;
// The doze screen brightness.
- private final float mScreenBrightnessDozeConfig;
+ private float mScreenBrightnessDozeConfig;
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -549,7 +550,7 @@
// DOZE AND DIM SETTINGS
mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -931,6 +932,8 @@
HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext, mHandler);
@@ -2017,7 +2020,7 @@
final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
final String displayUniqueId = mDisplayDevice.getUniqueId();
- final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ final HighBrightnessModeData hbmData =
ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
@@ -3251,7 +3254,7 @@
HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
- float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+ float brightnessMax, HighBrightnessModeData hbmData,
HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
Context context) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 47176fe..da9eef2 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -36,8 +36,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.DisplayManagerService.Clock;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 4791cd1..d3b41b8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,12 +16,17 @@
package com.android.server.display;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.feature.flags.FeatureFlags;
+import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -198,14 +203,14 @@
private final DisplayIdProducer mIdProducer = (isDefault) ->
isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
private Layout mCurrentLayout = null;
- private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
- private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
- private int mDeviceStateToBeAppliedAfterBoot =
- DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+ private DeviceState mDeviceState = INVALID_DEVICE_STATE;
+ private DeviceState mPendingDeviceState = INVALID_DEVICE_STATE;
+ private DeviceState mDeviceStateToBeAppliedAfterBoot = INVALID_DEVICE_STATE;
private boolean mBootCompleted = false;
private boolean mInteractive;
private final DisplayManagerFlags mFlags;
private final SyntheticModeManager mSyntheticModeManager;
+ private final FeatureFlags mDeviceStateManagerFlags;
LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
FoldGracePeriodProvider foldGracePeriodProvider,
@@ -245,6 +250,7 @@
mDeviceStateToLayoutMap = deviceStateToLayoutMap;
mFlags = flags;
mSyntheticModeManager = syntheticModeManager;
+ mDeviceStateManagerFlags = new FeatureFlagsImpl();
}
@Override
@@ -403,8 +409,8 @@
// Retrieve the display info for the display that matches the display id.
final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
if (device == null) {
- Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
- + " for the display state " + mDeviceState);
+ Slog.w(TAG, "The display device (" + display.getAddress()
+ + "), is not available for the display state " + mDeviceState.getIdentifier());
return null;
}
LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
@@ -431,9 +437,11 @@
ipw.println("mBootCompleted=" + mBootCompleted);
ipw.println();
- ipw.println("mDeviceState=" + mDeviceState);
- ipw.println("mPendingDeviceState=" + mPendingDeviceState);
- ipw.println("mDeviceStateToBeAppliedAfterBoot=" + mDeviceStateToBeAppliedAfterBoot);
+
+ ipw.println("mDeviceState=" + mDeviceState.getIdentifier());
+ ipw.println("mPendingDeviceState=" + mPendingDeviceState.getIdentifier());
+ ipw.println("mDeviceStateToBeAppliedAfterBoot="
+ + mDeviceStateToBeAppliedAfterBoot.getIdentifier());
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
@@ -463,7 +471,7 @@
mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId);
}
- void setDeviceStateLocked(int state) {
+ void setDeviceStateLocked(DeviceState state) {
if (!mBootCompleted) {
// The boot animation might still be in progress, we do not want to switch states now
// as the boot animation would end up with an incorrect size.
@@ -475,15 +483,17 @@
return;
}
- Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
- + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
+ Slog.i(TAG, "Requesting Transition to state: " + state + ", from state="
+ + mDeviceState.getIdentifier() + ", interactive=" + mInteractive
+ + ", mBootCompleted=" + mBootCompleted);
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
// temporarily turned off.
- resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
+ resetLayoutLocked(mDeviceState.getIdentifier(),
+ state.getIdentifier(), /* transitionValue= */ true);
mPendingDeviceState = state;
- mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+ mDeviceStateToBeAppliedAfterBoot = INVALID_DEVICE_STATE;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -498,7 +508,7 @@
}
if (DEBUG) {
- Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState);
+ Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState.getIdentifier());
}
// Send the transitioning phase updates to DisplayManager so that the displays can
// start turning OFF in preparation for the new layout.
@@ -533,8 +543,7 @@
void onBootCompleted() {
synchronized (mSyncRoot) {
mBootCompleted = true;
- if (mDeviceStateToBeAppliedAfterBoot
- != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
+ if (!mDeviceStateToBeAppliedAfterBoot.equals(INVALID_DEVICE_STATE)) {
setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot);
}
}
@@ -563,11 +572,18 @@
* @see #setDeviceStateLocked
*/
@VisibleForTesting
- boolean shouldDeviceBeWoken(int pendingState, int currentState, boolean isInteractive,
- boolean isBootCompleted) {
- return mDeviceStatesOnWhichToWakeUp.get(pendingState)
- && !mDeviceStatesOnWhichToWakeUp.get(currentState)
- && !isInteractive && isBootCompleted;
+ boolean shouldDeviceBeWoken(DeviceState pendingState, DeviceState currentState,
+ boolean isInteractive, boolean isBootCompleted) {
+ if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
+ && !currentState.equals(INVALID_DEVICE_STATE)
+ && !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
+ && !isInteractive && isBootCompleted;
+ } else {
+ return mDeviceStatesOnWhichToWakeUp.get(pendingState.getIdentifier())
+ && !mDeviceStatesOnWhichToWakeUp.get(currentState.getIdentifier())
+ && !isInteractive && isBootCompleted;
+ }
}
/**
@@ -588,14 +604,22 @@
* @see #setDeviceStateLocked
*/
@VisibleForTesting
- boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isInteractive,
- boolean isBootCompleted) {
- return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
- && mDeviceStatesOnWhichToSelectiveSleep.get(pendingState)
- && !mDeviceStatesOnWhichToSelectiveSleep.get(currentState)
- && isInteractive
- && isBootCompleted
- && !mFoldSettingProvider.shouldStayAwakeOnFold();
+ boolean shouldDeviceBePutToSleep(DeviceState pendingState, DeviceState currentState,
+ boolean isInteractive, boolean isBootCompleted) {
+ if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP)
+ && !currentState.equals(INVALID_DEVICE_STATE)
+ && !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP)
+ && isInteractive
+ && isBootCompleted
+ && !mFoldSettingProvider.shouldStayAwakeOnFold();
+ } else {
+ return mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
+ && !mDeviceStatesOnWhichToSelectiveSleep.get(currentState.getIdentifier())
+ && isInteractive
+ && isBootCompleted
+ && !mFoldSettingProvider.shouldStayAwakeOnFold();
+ }
}
private boolean areAllTransitioningDisplaysOffLocked() {
@@ -618,27 +642,25 @@
}
private void transitionToPendingStateLocked() {
- resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false);
+ resetLayoutLocked(mDeviceState.getIdentifier(),
+ mPendingDeviceState.getIdentifier(), /* transitionValue= */ false);
mDeviceState = mPendingDeviceState;
- mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+ mPendingDeviceState = INVALID_DEVICE_STATE;
applyLayoutLocked();
updateLogicalDisplaysLocked();
}
private void finishStateTransitionLocked(boolean force) {
- if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
+ if (mPendingDeviceState.equals(INVALID_DEVICE_STATE)) {
return;
}
- final boolean waitingToWakeDevice = mDeviceStatesOnWhichToWakeUp.get(mPendingDeviceState)
- && !mDeviceStatesOnWhichToWakeUp.get(mDeviceState)
- && !mInteractive && mBootCompleted;
+ final boolean waitingToWakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
+ mInteractive, mBootCompleted);
// The device should only wait for sleep if #shouldStayAwakeOnFold method returns false.
// If not, device should be marked ready for transition immediately.
- final boolean waitingToSleepDevice = mDeviceStatesOnWhichToSelectiveSleep.get(
- mPendingDeviceState)
- && !mDeviceStatesOnWhichToSelectiveSleep.get(mDeviceState)
- && mInteractive && mBootCompleted && !shouldStayAwakeOnFold();
+ final boolean waitingToSleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState,
+ mDeviceState, mInteractive, mBootCompleted) && !shouldStayAwakeOnFold();
final boolean displaysOff = areAllTransitioningDisplaysOffLocked();
final boolean isReadyToTransition = displaysOff && !waitingToWakeDevice
@@ -1104,7 +1126,7 @@
*/
private void applyLayoutLocked() {
final Layout oldLayout = mCurrentLayout;
- mCurrentLayout = mDeviceStateToLayoutMap.get(mDeviceState);
+ mCurrentLayout = mDeviceStateToLayoutMap.get(mDeviceState.getIdentifier());
Slog.i(TAG, "Applying layout: " + mCurrentLayout + ", Previous layout: " + oldLayout);
// Go through each of the displays in the current layout set.
@@ -1120,7 +1142,7 @@
final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
if (device == null) {
Slog.w(TAG, "applyLayoutLocked: The display device (" + address + "), is not "
- + "available for the display state " + mDeviceState);
+ + "available for the display state " + mDeviceState.getIdentifier());
continue;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 902daa4..5c2db35 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -133,7 +133,7 @@
// new token not null and hdr min % of the screen is set, subscribe.
// e.g. for virtual display, HBM data will be missing and HdrListener
// should not be registered
- if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
+ if (displayToken != null && mHdrListener.mHdrMinPixels >= 0 && hasBrightnessLimits()) {
mHdrListener.register(displayToken);
mRegisteredDisplayToken = displayToken;
}
@@ -179,6 +179,10 @@
pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
}
+ private boolean hasBrightnessLimits() {
+ return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty();
+ }
+
private void reset() {
if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
&& mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
@@ -214,11 +218,11 @@
mDesiredMaxBrightness = expectedMaxBrightness;
long debounceTime;
if (mDesiredMaxBrightness > mMaxBrightness) {
- debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
- mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
+ debounceTime = mHdrBrightnessData.brightnessIncreaseDebounceMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampIncrease;
} else {
- debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
- mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
+ debounceTime = mHdrBrightnessData.brightnessDecreaseDebounceMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampDecrease;
}
mHandler.removeCallbacks(mDebouncer);
@@ -232,7 +236,7 @@
float foundAmbientBoundary = Float.MAX_VALUE;
float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
for (Map.Entry<Float, Float> brightnessPoint :
- data.mMaxBrightnessLimits.entrySet()) {
+ data.maxBrightnessLimits.entrySet()) {
float ambientBoundary = brightnessPoint.getKey();
// find ambient lux upper boundary closest to current ambient lux
if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
diff --git a/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java b/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java
new file mode 100644
index 0000000..5b4e8d5
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.Spline;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.Function;
+
+public class DisplayDeviceConfigUtils {
+ private static final String TAG = "DisplayDeviceConfigUtils";
+
+ /**
+ * Create Spline from generic data
+ * @param points - points for Spline in format (x0, y0), (x1, y1) etc
+ * @param xExtractor - extract X component from generic data
+ * @param yExtractor - extract Y component from generic data
+ */
+ @Nullable
+ public static <T> Spline createSpline(List<T> points, Function<T, BigDecimal> xExtractor,
+ Function<T, BigDecimal> yExtractor) {
+ int size = points.size();
+ if (size == 0) {
+ return null;
+ }
+
+ float[] x = new float[size];
+ float[] y = new float[size];
+
+ int i = 0;
+ for (T point : points) {
+ x[i] = xExtractor.apply(point).floatValue();
+ if (i > 0) {
+ if (x[i] <= x[i - 1]) {
+ Slog.e(TAG, "spline control points must be strictly increasing, ignoring "
+ + "configuration. x: " + x[i] + " <= " + x[i - 1]);
+ return null;
+ }
+ }
+ y[i] = yExtractor.apply(point).floatValue();
+ ++i;
+ }
+
+ return Spline.createSpline(x, y);
+ }
+}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 837fbf7..c940807 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -16,63 +16,138 @@
package com.android.server.display.config;
+import static com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+
import android.annotation.Nullable;
+import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import java.math.BigDecimal;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Brightness config for HDR content
+ * <pre>
+ * {@code
+ * <displayConfiguration>
+ * ...
+ * <hdrBrightnessConfig>
+ * <brightnessMap>
+ * <point>
+ * <first>500</first>
+ * <second>0.3</second>
+ * </point>
+ * <point>
+ * <first>1200</first>
+ * <second>0.6</second>
+ * </point>
+ * </brightnessMap>
+ * <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
+ * <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ * <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
+ * <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ * <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
+ * <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
+ * <allowInLowPowerMode>true</allowInLowPowerMode>
+ * <sdrHdrRatioMap>
+ * <point>
+ * <first>2.0</first>
+ * <second>4.0</second>
+ * </point>
+ * <point>
+ * <first>100</first>
+ * <second>8.0</second>
+ * </point>
+ * </sdrHdrRatioMap>
+ * </hdrBrightnessConfig>
+ * ...
+ * </displayConfiguration>
+ * }
+ * </pre>
*/
public class HdrBrightnessData {
+ private static final String TAG = "HdrBrightnessData";
/**
* Lux to brightness map
*/
- public final Map<Float, Float> mMaxBrightnessLimits;
+ public final Map<Float, Float> maxBrightnessLimits;
/**
* Debounce time for brightness increase
*/
- public final long mBrightnessIncreaseDebounceMillis;
+ public final long brightnessIncreaseDebounceMillis;
/**
* Brightness increase animation speed
*/
- public final float mScreenBrightnessRampIncrease;
+ public final float screenBrightnessRampIncrease;
/**
* Debounce time for brightness decrease
*/
- public final long mBrightnessDecreaseDebounceMillis;
+ public final long brightnessDecreaseDebounceMillis;
/**
* Brightness decrease animation speed
*/
- public final float mScreenBrightnessRampDecrease;
+ public final float screenBrightnessRampDecrease;
+
+ /**
+ * Min Hdr layer size to start hdr brightness boost up to high brightness mode transition point
+ */
+ public final float minimumHdrPercentOfScreenForNbm;
+
+ /**
+ * Min Hdr layer size to start hdr brightness boost above high brightness mode transition point
+ */
+ public final float minimumHdrPercentOfScreenForHbm;
+
+ /**
+ * If Hdr brightness boost allowed in low power mode
+ */
+ public final boolean allowInLowPowerMode;
+
+ /**
+ * brightness to boost ratio spline
+ */
+ @Nullable
+ public final Spline sdrToHdrRatioSpline;
@VisibleForTesting
public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
- long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
- mMaxBrightnessLimits = maxBrightnessLimits;
- mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
- mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
- mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
- mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
+ long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease,
+ float minimumHdrPercentOfScreenForNbm, float minimumHdrPercentOfScreenForHbm,
+ boolean allowInLowPowerMode, @Nullable Spline sdrToHdrRatioSpline) {
+ this.maxBrightnessLimits = maxBrightnessLimits;
+ this.brightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
+ this.screenBrightnessRampIncrease = screenBrightnessRampIncrease;
+ this.brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
+ this.screenBrightnessRampDecrease = screenBrightnessRampDecrease;
+ this.minimumHdrPercentOfScreenForNbm = minimumHdrPercentOfScreenForNbm;
+ this.minimumHdrPercentOfScreenForHbm = minimumHdrPercentOfScreenForHbm;
+ this.allowInLowPowerMode = allowInLowPowerMode;
+ this.sdrToHdrRatioSpline = sdrToHdrRatioSpline;
}
@Override
public String toString() {
return "HdrBrightnessData {"
- + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
- + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
- + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
- + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
- + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
+ + "mMaxBrightnessLimits: " + maxBrightnessLimits
+ + ", mBrightnessIncreaseDebounceMillis: " + brightnessIncreaseDebounceMillis
+ + ", mScreenBrightnessRampIncrease: " + screenBrightnessRampIncrease
+ + ", mBrightnessDecreaseDebounceMillis: " + brightnessDecreaseDebounceMillis
+ + ", mScreenBrightnessRampDecrease: " + screenBrightnessRampDecrease
+ + ", minimumHdrPercentOfScreenForNbm: " + minimumHdrPercentOfScreenForNbm
+ + ", minimumHdrPercentOfScreenForHbm: " + minimumHdrPercentOfScreenForHbm
+ + ", allowInLowPowerMode: " + allowInLowPowerMode
+ + ", sdrToHdrRatioSpline: " + sdrToHdrRatioSpline
+ "} ";
}
@@ -83,7 +158,7 @@
public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
if (hdrConfig == null) {
- return null;
+ return getFallbackData(config.getHighBrightnessMode());
}
List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
@@ -92,10 +167,59 @@
brightnessLimits.put(point.getFirst().floatValue(), point.getSecond().floatValue());
}
+ float minHdrPercentForHbm = hdrConfig.getMinimumHdrPercentOfScreenForHbm() != null
+ ? hdrConfig.getMinimumHdrPercentOfScreenForHbm().floatValue()
+ : getFallbackHdrPercent(config.getHighBrightnessMode());
+
+ float minHdrPercentForNbm = hdrConfig.getMinimumHdrPercentOfScreenForNbm() != null
+ ? hdrConfig.getMinimumHdrPercentOfScreenForNbm().floatValue() : minHdrPercentForHbm;
+
return new HdrBrightnessData(brightnessLimits,
hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
- hdrConfig.getScreenBrightnessRampDecrease().floatValue());
+ hdrConfig.getScreenBrightnessRampDecrease().floatValue(),
+ minHdrPercentForNbm, minHdrPercentForHbm, hdrConfig.getAllowInLowPowerMode(),
+ getSdrHdrRatioSpline(hdrConfig, config.getHighBrightnessMode()));
+ }
+
+ @Nullable
+ private static HdrBrightnessData getFallbackData(HighBrightnessMode hbm) {
+ if (hbm == null) {
+ return null;
+ }
+ float fallbackPercent = getFallbackHdrPercent(hbm);
+ Spline fallbackSpline = getFallbackSdrHdrRatioSpline(hbm);
+ return new HdrBrightnessData(Collections.emptyMap(),
+ 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+ 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+ fallbackPercent, fallbackPercent, false, fallbackSpline);
+ }
+
+ private static float getFallbackHdrPercent(HighBrightnessMode hbm) {
+ BigDecimal minHdrPctOfScreen = hbm != null ? hbm.getMinimumHdrPercentOfScreen_all() : null;
+ return minHdrPctOfScreen != null ? minHdrPctOfScreen.floatValue()
+ : HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+ }
+
+ @Nullable
+ private static Spline getSdrHdrRatioSpline(HdrBrightnessConfig hdrConfig,
+ HighBrightnessMode hbm) {
+ NonNegativeFloatToFloatMap sdrHdrRatioMap = hdrConfig.getSdrHdrRatioMap();
+ if (sdrHdrRatioMap == null) {
+ return getFallbackSdrHdrRatioSpline(hbm);
+ }
+ return DisplayDeviceConfigUtils.createSpline(sdrHdrRatioMap.getPoint(),
+ NonNegativeFloatToFloatPoint::getFirst, NonNegativeFloatToFloatPoint::getSecond);
+ }
+
+ @Nullable
+ private static Spline getFallbackSdrHdrRatioSpline(HighBrightnessMode hbm) {
+ SdrHdrRatioMap fallbackMap = hbm != null ? hbm.getSdrHdrRatioMap_all() : null;
+ if (fallbackMap == null) {
+ return null;
+ }
+ return DisplayDeviceConfigUtils.createSpline(fallbackMap.getPoint(),
+ SdrHdrRatioPoint::getSdrNits, SdrHdrRatioPoint::getHdrRatio);
}
}
diff --git a/services/core/java/com/android/server/display/config/HighBrightnessModeData.java b/services/core/java/com/android/server/display/config/HighBrightnessModeData.java
new file mode 100644
index 0000000..dc2f976
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HighBrightnessModeData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.Spline;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.math.BigDecimal;
+import java.util.function.Function;
+
+/**
+ * Container for high brightness mode configuration data.
+ */
+public class HighBrightnessModeData {
+ private static final String TAG = "HighBrightnessModeData";
+
+ static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
+ /** Minimum lux needed to enter high brightness mode */
+ public final float minimumLux;
+
+ /** Brightness level at which we transition from normal to high-brightness. */
+ public final float transitionPoint;
+
+ /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
+ public final boolean allowInLowPowerMode;
+
+ /** Time window for HBM. */
+ public final long timeWindowMillis;
+
+ /** Maximum time HBM is allowed to be during in a {@code timeWindowMillis}. */
+ public final long timeMaxMillis;
+
+ /** Minimum time that HBM can be on before being enabled. */
+ public final long timeMinMillis;
+
+ /** Minimum HDR video size to enter high brightness mode */
+ public final float minimumHdrPercentOfScreen;
+
+ @Nullable
+ public final Spline sdrToHdrRatioSpline;
+
+ @Nullable
+ public final SurfaceControl.RefreshRateRange refreshRateLimit;
+
+ public final boolean isHighBrightnessModeEnabled;
+
+ @VisibleForTesting
+ public HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
+ long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
+ float minimumHdrPercentOfScreen, @Nullable Spline sdrToHdrRatioSpline,
+ @Nullable SurfaceControl.RefreshRateRange refreshRateLimit,
+ boolean isHighBrightnessModeEnabled) {
+ this.minimumLux = minimumLux;
+ this.transitionPoint = transitionPoint;
+ this.timeWindowMillis = timeWindowMillis;
+ this.timeMaxMillis = timeMaxMillis;
+ this.timeMinMillis = timeMinMillis;
+ this.allowInLowPowerMode = allowInLowPowerMode;
+ this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
+ this.sdrToHdrRatioSpline = sdrToHdrRatioSpline;
+ this.refreshRateLimit = refreshRateLimit;
+ this.isHighBrightnessModeEnabled = isHighBrightnessModeEnabled;
+ }
+
+ @Override
+ public String toString() {
+ return "HBM{"
+ + "minLux: " + minimumLux
+ + ", transition: " + transitionPoint
+ + ", timeWindow: " + timeWindowMillis + "ms"
+ + ", timeMax: " + timeMaxMillis + "ms"
+ + ", timeMin: " + timeMinMillis + "ms"
+ + ", allowInLowPowerMode: " + allowInLowPowerMode
+ + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
+ + ", mSdrToHdrRatioSpline=" + sdrToHdrRatioSpline
+ + ", refreshRateLimit=" + refreshRateLimit
+ + ", isHighBrightnessModeEnabled=" + isHighBrightnessModeEnabled
+ + "} ";
+ }
+
+ /**
+ * Loads HighBrightnessModeData from DisplayConfiguration
+ */
+ public static HighBrightnessModeData loadHighBrightnessModeData(DisplayConfiguration config,
+ Function<HighBrightnessMode, Float> transitionPointProvider) {
+ final HighBrightnessMode hbm = config.getHighBrightnessMode();
+ float minimumLux = 0f;
+ float transitionPoint = 0f;
+ long timeWindowMillis = 0L;
+ long timeMaxMillis = 0L;
+ long timeMinMillis = 0L;
+ boolean allowInLowPowerMode = false;
+ float minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+ Spline sdrToHdrRatioSpline = null;
+ SurfaceControl.RefreshRateRange refreshRateLimit = null;
+ boolean isEnabled = false;
+
+ if (hbm != null) {
+ minimumLux = hbm.getMinimumLux_all().floatValue();
+ transitionPoint = transitionPointProvider.apply(hbm);
+ HbmTiming hbmTiming = hbm.getTiming_all();
+ timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
+ timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
+ timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
+ allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
+ BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all();
+ if (minHdrPctOfScreen != null) {
+ minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue();
+ if (minimumHdrPercentOfScreen > 1 || minimumHdrPercentOfScreen < 0) {
+ Slog.w(TAG, "Invalid minimum HDR percent of screen: "
+ + minimumHdrPercentOfScreen);
+ minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+ }
+ }
+
+ sdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm);
+ RefreshRateRange rr = hbm.getRefreshRate_all();
+ if (rr != null) {
+ refreshRateLimit = new SurfaceControl.RefreshRateRange(
+ rr.getMinimum().floatValue(), rr.getMaximum().floatValue());
+ }
+ isEnabled = hbm.getEnabled();
+ }
+ return new HighBrightnessModeData(minimumLux, transitionPoint,
+ timeWindowMillis, timeMaxMillis, timeMinMillis, allowInLowPowerMode,
+ minimumHdrPercentOfScreen, sdrToHdrRatioSpline, refreshRateLimit, isEnabled);
+
+ }
+
+ private static Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) {
+ final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all();
+ if (sdrHdrRatioMap == null) {
+ return null;
+ }
+ return DisplayDeviceConfigUtils.createSpline(sdrHdrRatioMap.getPoint(),
+ SdrHdrRatioPoint::getSdrNits, SdrHdrRatioPoint::getHdrRatio);
+ }
+}
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 3ce7d2a..e1934b0 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -154,6 +154,10 @@
Flags::useFusionProxSensor
);
+ private final FlagState mDozeBrightnessFloat = new FlagState(
+ Flags.FLAG_DOZE_BRIGHTNESS_FLOAT,
+ Flags::dozeBrightnessFloat);
+
private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
Flags::offloadControlsDozeAutoBrightness
@@ -347,6 +351,10 @@
return mUseFusionProxSensor.getName();
}
+ public boolean isDozeBrightnessFloatEnabled() {
+ return mDozeBrightnessFloat.isEnabled();
+ }
+
/**
* @return Whether DisplayOffload should control auto-brightness in doze
*/
@@ -415,6 +423,7 @@
pw.println(" " + mRefactorDisplayPowerController);
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
+ pw.println(" " + mDozeBrightnessFloat);
pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
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 fd3af23..ac5f97f 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
@@ -246,6 +246,14 @@
}
flag {
+ name: "doze_brightness_float"
+ namespace: "display_manager"
+ description: "Define doze brightness in the float scale [0, 1]."
+ bug: "343796384"
+ is_fixed_read_only: true
+}
+
+flag {
name: "offload_controls_doze_auto_brightness"
namespace: "display_manager"
description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 88da6fb..550f68f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -660,7 +660,11 @@
.setPortId(physicalAddressToPortId(physicalAddress))
.setDeviceType(type)
.build();
- updateCecDevice(updatedDeviceInfo);
+ if (deviceInfo.getPhysicalAddress() != physicalAddress) {
+ addCecDevice(updatedDeviceInfo);
+ } else {
+ updateCecDevice(updatedDeviceInfo);
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 82ecb4a..8ca0458 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -18,20 +18,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.Process;
import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
@@ -225,49 +219,17 @@
sWriter.startThread();
}
- static void initialize(@NonNull Handler handler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- handler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserCreated(UserInfo user, @Nullable Object token) {
- final int userId = user.id;
- sWriter.onUserCreated(userId);
- handler.post(() -> {
- synchronized (ImfLock.class) {
- if (!sPerUserMap.contains(userId)) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeUtils.load(userId);
- sPerUserMap.put(userId, additionalSubtypeMap);
- final InputMethodSettings settings =
- InputMethodManagerService
- .queryInputMethodServicesInternal(context,
- userId,
- additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
- }
- }
- });
- }
+ @AnyThread
+ static void onUserCreated(@UserIdInt int userId) {
+ sWriter.onUserCreated(userId);
+ }
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- sWriter.onUserRemoved(userId);
- handler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
+ sWriter.onUserRemoved(userId);
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
- }
+ sPerUserMap.remove(userId);
}
});
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 3f28c47..a7280e6 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -138,6 +138,9 @@
@PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
boolean isInputMethodPickerShownForTest();
+ @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ void onImeSwitchButtonClickFromSystem(int displayId);
+
InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
@@ -344,6 +347,14 @@
return mCallback.isInputMethodPickerShownForTest();
}
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ super.onImeSwitchButtonClickFromSystem_enforcePermission();
+
+ mCallback.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
@Override
public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
return mCallback.getCurrentInputMethodSubtype(userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
}
- mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+ mService.initializeImeLocked(mCurMethod, mCurToken,
+ InputMethodBindingController.this);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked(mUserId);
mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+ private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+ private static final String SYSTEM_PACKAGE_NAME = "android";
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodDrawsNavBarResourceMonitor() {
+ }
+
+ @WorkerThread
+ static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+ final Context userAwareContext;
+ if (context.getUserId() == userId) {
+ userAwareContext = context;
+ } else {
+ userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ }
+ try {
+ return userAwareContext.getPackageManager()
+ .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+ .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+ e);
+ return false;
+ }
+ }
+
+ @FunctionalInterface
+ interface OnUpdateCallback {
+ void onUpdate(@UserIdInt int userId);
+ }
+
+ @SuppressLint("MissingPermission")
+ @AnyThread
+ static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+ @NonNull OnUpdateCallback callback) {
+ final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = getSendingUserId();
+ callback.onUpdate(userId);
+ }
+ };
+ context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+ null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0dbaaf3..c093c22 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -26,6 +26,7 @@
import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
@@ -52,6 +53,7 @@
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -66,6 +68,7 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -78,6 +81,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
@@ -88,6 +92,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -166,14 +171,12 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
@@ -199,7 +202,6 @@
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -334,6 +336,7 @@
final Resources mRes;
private final Handler mHandler;
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
@NonNull
private final Handler mIoHandler;
@@ -343,8 +346,8 @@
private int mCurrentUserId;
/** Holds all user related data */
- @GuardedBy("ImfLock.class")
- private UserDataRepository mUserDataRepository;
+ @SharedByAllUsersField
+ private final UserDataRepository mUserDataRepository;
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
@@ -395,15 +398,6 @@
@SharedByAllUsersField
private IntArray mStylusIds;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
/**
* {@inheritDoc}
@@ -480,13 +474,13 @@
@SharedByAllUsersField
boolean mSystemReady;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
UserDataRepository.UserData getUserData(@UserIdInt int userId) {
return mUserDataRepository.getOrCreate(userId);
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
return getUserData(userId).mBindingController;
@@ -648,7 +642,7 @@
* Handles {@link Intent#ACTION_LOCALE_CHANGED}.
*
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
- * the users. We should ignore this event if this is about any background user's locale.</p>
+ * the users.</p>
*/
void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) {
if (DEBUG) {
@@ -665,13 +659,14 @@
AdditionalSubtypeMapRepository.get(userId),
DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, settings);
+
+ if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
+ // If the locale is changed, needs to reset the default ime
+ resetDefaultImeLocked(mContext, userId);
+ updateFromSettingsLocked(true, userId);
+ }
}
- // TODO(b/305849394): Dispatch this to non-current users.
- final int userId = mCurrentUserId;
- postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
- // If the locale is changed, needs to reset the default ime
- resetDefaultImeLocked(mContext, userId);
- updateFromSettingsLocked(true, userId);
}
}
@@ -919,21 +914,62 @@
* {@link SystemService} used to publish and manage the lifecycle of
* {@link InputMethodManagerService}.
*/
- public static final class Lifecycle extends SystemService {
+ public static final class Lifecycle extends SystemService
+ implements UserManagerInternal.UserLifecycleListener {
private final InputMethodManagerService mService;
-
public Lifecycle(Context context) {
- this(context, new InputMethodManagerService(context,
- shouldEnableConcurrentMultiUserMode(context)));
+ this(context, createServiceForProduction(context));
+
+ // For production code, hook up user lifecycle
+ mService.mUserManagerInternal.addUserLifecycleListener(this);
+
+ // Hook up resource change first before initializeUsersAsync() starts reading the
+ // seemingly initial data so that we can eliminate the race condition.
+ InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+ mService::onUpdateResourceOverlay);
+
+ // Also schedule user init tasks onto an I/O thread.
+ initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
}
- public Lifecycle(
- Context context, @NonNull InputMethodManagerService inputMethodManagerService) {
+ @VisibleForTesting
+ Lifecycle(Context context, @NonNull InputMethodManagerService inputMethodManagerService) {
super(context);
mService = inputMethodManagerService;
}
+ /**
+ * Does initialization then instantiate {@link InputMethodManagerService} for production
+ * configurations.
+ *
+ * <p>We have this abstraction just because several unit tests directly initialize
+ * {@link InputMethodManagerService} with some mocked/emulated dependencies.</p>
+ *
+ * @param context {@link Context} to be used to set up
+ * @return {@link InputMethodManagerService} object to be used
+ */
+ @NonNull
+ private static InputMethodManagerService createServiceForProduction(
+ @NonNull Context context) {
+ // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
+ // additional subtypes in switchUserOnHandlerLocked().
+ final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ thread.start();
+
+ final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ ioThread.start();
+
+ SecureSettingsWrapper.setContentResolver(context.getContentResolver());
+
+ return new InputMethodManagerService(context,
+ shouldEnableConcurrentMultiUserMode(context), thread.getLooper(),
+ Handler.createAsync(ioThread.getLooper()),
+ null /* bindingControllerForTesting */);
+ }
+
@Override
public void onStart() {
mService.publishLocalService();
@@ -975,6 +1011,24 @@
}
@Override
+ public void onUserCreated(UserInfo user, @Nullable Object token) {
+ // Called directly from UserManagerService. Do not block the calling thread.
+ final int userId = user.id;
+ AdditionalSubtypeMapRepository.onUserCreated(userId);
+ initializeUsersAsync(new int[userId]);
+ }
+
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ // Called directly from UserManagerService. Do not block the calling thread.
+ final int userId = user.id;
+ SecureSettingsWrapper.onUserRemoved(userId);
+ AdditionalSubtypeMapRepository.remove(userId, mService.mIoHandler);
+ InputMethodSettingsRepository.remove(userId);
+ mService.mUserDataRepository.remove(userId);
+ }
+
+ @Override
public void onUserUnlocking(@NonNull TargetUser user) {
// Called on ActivityManager thread.
SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
@@ -987,16 +1041,55 @@
// Called on ActivityManager thread.
final int userId = user.getUserIdentifier();
SecureSettingsWrapper.onUserStarting(userId);
- synchronized (ImfLock.class) {
- mService.getUserData(userId);
- if (mService.mConcurrentMultiUserModeEnabled) {
- if (mService.mCurrentUserId != userId && mService.mSystemReady) {
- mService.initializeVisibleBackgroundUserLocked(userId);
+ mService.mIoHandler.post(() -> {
+ synchronized (ImfLock.class) {
+ if (mService.mConcurrentMultiUserModeEnabled) {
+ if (mService.mCurrentUserId != userId && mService.mSystemReady) {
+ mService.initializeVisibleBackgroundUserLocked(userId);
+ }
}
}
- }
+ });
}
+ @AnyThread
+ private void initializeUsersAsync(@UserIdInt int[] userIds) {
+ mService.mIoHandler.post(() -> {
+ final var service = mService;
+ final var context = service.mContext;
+ final var userManagerInternal = service.mUserManagerInternal;
+
+ // We first create InputMethodMap for each user without loading AdditionalSubtypes.
+ final int numUsers = userIds.length;
+ final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
+ context, userId, AdditionalSubtypeMap.EMPTY_MAP,
+ DirectBootAwareness.AUTO).getMethodMap();
+ final int profileParentId = userManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+ profileParentId);
+ final var userData = mService.getUserData(userId);
+ userData.mImeDrawsNavBar.set(value);
+ }
+
+ // Then create full InputMethodMap for each user. Note that
+ // AdditionalSubtypeMapRepository#get() and InputMethodSettingsRepository#put()
+ // need to be called with ImfLock held (b/352387655).
+ // TODO(b/343601565): Avoid ImfLock after fixing b/352387655.
+ synchronized (ImfLock.class) {
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ final var map = AdditionalSubtypeMapRepository.get(userId);
+ final var methodMap = rawMethodMaps[i].applyAdditionalSubtypes(map);
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ }
+ });
+ }
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1041,46 +1134,20 @@
mHandler.post(task);
}
- public InputMethodManagerService(Context context,
- boolean concurrentMultiUserModeEnabled) {
- this(context, concurrentMultiUserModeEnabled, null, null, null);
- }
-
@VisibleForTesting
InputMethodManagerService(
Context context,
boolean concurrentMultiUserModeEnabled,
- @Nullable ServiceThread serviceThreadForTesting,
- @Nullable ServiceThread ioThreadForTesting,
+ @NonNull Looper uiLooper,
+ @NonNull Handler ioHandler,
@Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
synchronized (ImfLock.class) {
mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
mContext = context;
mRes = context.getResources();
- SecureSettingsWrapper.onStart(mContext);
- // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
- // additional subtypes in switchUserOnHandlerLocked().
- final ServiceThread thread =
- serviceThreadForTesting != null
- ? serviceThreadForTesting
- : new ServiceThread(
- HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND,
- true /* allowIo */);
- thread.start();
- mHandler = Handler.createAsync(thread.getLooper(), this);
- {
- final ServiceThread ioThread =
- ioThreadForTesting != null
- ? ioThreadForTesting
- : new ServiceThread(
- PACKAGE_MONITOR_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND,
- true /* allowIo */);
- ioThread.start();
- mIoHandler = Handler.createAsync(ioThread.getLooper());
- }
+ mHandler = Handler.createAsync(uiLooper, this);
+ mIoHandler = ioHandler;
SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
mImeTrackerService = new ImeTrackerService(mHandler);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
@@ -1095,26 +1162,13 @@
mShowOngoingImeSwitcherForPhones = false;
- // Executing InputMethodSettingsRepository.initialize() does not mean that it
- // immediately becomes ready to return the up-to-date InputMethodSettings for each
- // running user, because we want to return from the constructor as early as possible so
- // as not to delay the system boot process.
- // Search for InputMethodSettingsRepository.put() to find where and when it's actually
- // being updated. In general IMMS should refrain from exposing the existence of IMEs
- // until systemReady().
- InputMethodSettingsRepository.initialize(mHandler, mContext);
- AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
-
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
- mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+ mUserDataRepository = new UserDataRepository(
bindingControllerForTesting != null ? bindingControllerForTesting
: bindingControllerFactory);
- for (int id : mUserManagerInternal.getUserIds()) {
- getUserData(id);
- }
mMenuController = new InputMethodMenuController(this);
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
@@ -1128,9 +1182,10 @@
mNonPreemptibleInputMethods = mRes.getStringArray(
com.android.internal.R.array.config_nonPreemptibleInputMethods);
Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
- mHwController = new HandwritingModeController(mContext, thread.getLooper(),
+ mHwController = new HandwritingModeController(mContext, uiLooper,
new InkWindowInitializer(), discardDelegationTextRunnable);
registerDeviceListenerAndCheckStylusSupport();
+ mInputMethodManagerInternal = new LocalServiceImpl();
}
}
@@ -1192,36 +1247,6 @@
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
}
- @GuardedBy("ImfLock.class")
- private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
- // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
- // profile parent user.
- // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
- if (mImeDrawsImeNavBarRes != null
- && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
- mImeDrawsImeNavBarRes.close();
- mImeDrawsImeNavBarRes = null;
- }
- if (mImeDrawsImeNavBarRes == null) {
- final Context userContext;
- if (mContext.getUserId() == profileParentUserId) {
- userContext = mContext;
- } else {
- userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
- 0 /* flags */);
- }
- mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
- com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
- synchronized (ImfLock.class) {
- if (resource == mImeDrawsImeNavBarRes) {
- sendOnNavButtonFlagsChangedLocked();
- }
- }
- });
- }
- }
-
@NonNull
private static PackageManager getPackageManagerForUser(@NonNull Context context,
@UserIdInt int userId) {
@@ -1254,8 +1279,6 @@
// Hereafter we start initializing things for "newUserId".
- maybeInitImeNavbarConfigLocked(newUserId);
-
final var newUserData = getUserData(newUserId);
// TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1334,23 +1357,6 @@
});
}
- // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
- mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
- // Note that the synchronization block below guarantees that the task
- // can never be completed before the returned Future<?> object is assigned to
- // the "mImeDrawsImeNavBarResLazyInitFuture" field.
- synchronized (ImfLock.class) {
- mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mCurrentUserId) {
- // This means that the current user is already switched to other user
- // before the background task is executed. In this scenario the relevant
- // field should already be initialized.
- return;
- }
- maybeInitImeNavbarConfigLocked(currentUserId);
- }
- }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
new String[] {
@@ -1386,9 +1392,7 @@
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
- final var unused = SystemServerInitThreadPool.submit(
- AdditionalSubtypeMapRepository::startWriterThread,
- "Start AdditionalSubtypeMapRepository's writer thread");
+ AdditionalSubtypeMapRepository.startWriterThread();
if (mConcurrentMultiUserModeEnabled) {
for (int userId : mUserManagerInternal.getUserIds()) {
@@ -1872,7 +1876,8 @@
userData.mCurClient.mUid, true /* direct */);
}
- @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+ @InputMethodNavButtonFlags final int navButtonFlags =
+ getInputMethodNavButtonFlagsLocked(userData);
final SessionState session = userData.mCurClient.mCurSession;
setEnabledSessionLocked(session, userData);
session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2264,15 +2269,15 @@
@GuardedBy("ImfLock.class")
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull InputMethodBindingController bindingController) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + getInputMethodBindingController(userId).getCurTokenDisplayId());
+ + bindingController.getCurTokenDisplayId());
}
+ final int userId = bindingController.getUserId();
inputMethod.initializeInternal(token,
new InputMethodPrivilegedOperationsImpl(this, token, userId),
- // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
- getInputMethodNavButtonFlagsLocked());
+ getInputMethodNavButtonFlagsLocked(getUserData(userId)));
}
@AnyThread
@@ -2571,23 +2576,17 @@
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked() {
- // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
- final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
- if (mImeDrawsImeNavBarResLazyInitFuture != null) {
- // TODO(b/225366708): Avoid Future.get(), which is internally used here.
- ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
- "Waiting for the lazy init of mImeDrawsImeNavBarRes");
- }
+ private int getInputMethodNavButtonFlagsLocked(
+ @NonNull UserDataRepository.UserData userData) {
+ final int userId = userData.mUserId;
+ final var bindingController = userData.mBindingController;
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
final int tokenDisplayId = bindingController.getCurTokenDisplayId();
final boolean hasNavigationBar = mWindowManagerInternal
.hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
? tokenDisplayId : DEFAULT_DISPLAY);
- final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+ final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2931,7 +2930,7 @@
final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
}
@GuardedBy("ImfLock.class")
@@ -3969,6 +3968,29 @@
}
}
+ @BinderThread
+ private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
+ @UserIdInt int userId) {
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, false,
+ ActivityManagerInternal.ALLOW_FULL_ONLY, "onImeSwitchButtonClickFromClient", null);
+
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(token, userId)) {
+ return;
+ }
+ showInputMethodPickerFromSystem(
+ InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
+ }
+ }
+
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ showInputMethodPickerFromSystem(
+ InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
+ }
+
@NonNull
private static IllegalArgumentException getExceptionForUnknownImeId(
@Nullable String imeId) {
@@ -4114,7 +4136,8 @@
final var currentImi = bindingController.getSelectedMethod();
final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
.getNextInputMethodLocked(onlyCurrentIme, currentImi,
- bindingController.getCurrentSubtype());
+ bindingController.getCurrentSubtype(),
+ MODE_AUTO, true /* forward */);
if (nextSubtype == null) {
return false;
}
@@ -4134,7 +4157,8 @@
final var currentImi = bindingController.getSelectedMethod();
final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
.getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
- bindingController.getCurrentSubtype());
+ bindingController.getCurrentSubtype(),
+ MODE_AUTO, true /* forward */);
return nextSubtype != null;
}
}
@@ -4558,6 +4582,7 @@
proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
+ proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled);
proto.end(token);
}
}
@@ -4954,7 +4979,7 @@
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
synchronized (ImfLock.class) {
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
case MSG_SYSTEM_UNLOCK_USER: {
@@ -5288,7 +5313,7 @@
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5297,14 +5322,38 @@
}
@GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked() {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ void sendOnNavButtonFlagsChangedToAllImesLocked() {
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
}
- curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+ curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+ }
+
+ @WorkerThread
+ private void onUpdateResourceOverlay(@UserIdInt int userId) {
+ final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+ final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+ final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+ for (int profileUserId : profileUserIds) {
+ final var userData = getUserData(profileUserId);
+ userData.mImeDrawsNavBar.set(value);
+ updatedUsers.add(userData);
+ }
+ synchronized (ImfLock.class) {
+ updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+ }
}
@GuardedBy("ImfLock.class")
@@ -5434,6 +5483,10 @@
// Set InputMethod here
settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
+
+ if (Flags.imeSwitcherRevamp()) {
+ getUserData(userId).mSwitchingController.onInputMethodSubtypeChanged();
+ }
}
@GuardedBy("ImfLock.class")
@@ -5554,11 +5607,29 @@
if (currentImi == null) {
return;
}
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, bindingController.getCurrentSubtype());
- final InputMethodSubtypeHandle nextSubtypeHandle =
- getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch(
+ final var currentSubtype = bindingController.getCurrentSubtype();
+ final InputMethodSubtypeHandle nextSubtypeHandle;
+ if (Flags.imeSwitcherRevamp()) {
+ final var nextItem = getUserData(userId).mSwitchingController
+ .getNextInputMethodForHardware(
+ false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO,
+ direction > 0 /* forward */);
+ if (nextItem == null) {
+ Slog.i(TAG, "Hardware keyboard switching shortcut,"
+ + " next input method and subtype not found");
+ return;
+ }
+
+ final var nextSubtype = nextItem.mSubtypeId > NOT_A_SUBTYPE_ID
+ ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeId) : null;
+ nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
+ } else {
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, currentSubtype);
+ nextSubtypeHandle =
+ getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch(
currentSubtypeHandle, direction > 0);
+ }
if (nextSubtypeHandle == null) {
return;
}
@@ -5585,7 +5656,12 @@
}
private void publishLocalService() {
- LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
+ LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
+ }
+
+ // TODO(b/352228316): Remove it once IMMIProxy is removed.
+ InputMethodManagerInternal getLocalService(){
+ return mInputMethodManagerInternal;
}
private final class LocalServiceImpl extends InputMethodManagerInternal {
@@ -6006,6 +6082,7 @@
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
p.println("Current Input Method Manager state:");
+ p.println(" concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled);
final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
p.println(" Input Methods:");
@@ -6066,6 +6143,7 @@
u.mImeBindingState.dump(" ", p);
p.println(" enabledSession=" + u.mEnabledSession);
p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
p.println(" switchingController:");
u.mSwitchingController.dump(p, " ");
p.println(" mLastEnabledInputMethodsStr="
@@ -6875,6 +6953,12 @@
@BinderThread
@Override
+ public void onImeSwitchButtonClickFromClient(int displayId, @UserIdInt int userId) {
+ mImms.onImeSwitchButtonClickFromClient(mToken, displayId, userId);
+ }
+
+ @BinderThread
+ @Override
public void notifyUserActionAsync() {
mImms.notifyUserAction(mToken, mUserId);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
mSwitchingDialog.show();
}
@@ -242,7 +242,7 @@
// TODO(b/305849394): Make InputMethodMenuController multi-user aware
final int userId = mService.getCurrentImeUserIdLocked();
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 68924b5..50ba364 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -16,17 +16,12 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
final class InputMethodSettingsRepository {
@GuardedBy("ImfLock.class")
@@ -54,33 +49,10 @@
sPerUserMap.put(userId, obj);
}
- static void initialize(@NonNull Handler handler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- handler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- handler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
- synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- final InputMethodSettings settings =
- InputMethodManagerService.queryInputMethodServicesInternal(
- context,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- put(userId, settings);
- }
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId) {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index bb1b9df..05cc598 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -24,11 +26,14 @@
import android.util.ArraySet;
import android.util.Printer;
import android.util.Slog;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -45,6 +50,34 @@
private static final boolean DEBUG = false;
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ @IntDef(prefix = {"MODE_"}, value = {
+ MODE_STATIC,
+ MODE_RECENT,
+ MODE_AUTO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SwitchMode {
+ }
+
+ /**
+ * Switch using the static order (the order of the given list of input methods and subtypes).
+ * This order is only set when given a new list, and never updated.
+ */
+ public static final int MODE_STATIC = 0;
+
+ /**
+ * Switch using the recency based order, going from most recent to least recent,
+ * updated on {@link #onUserActionLocked user action}.
+ */
+ public static final int MODE_RECENT = 1;
+
+ /**
+ * If there was a {@link #onUserActionLocked user action} since the last
+ * {@link #onInputMethodSubtypeChanged() switch}, and direction is forward,
+ * use {@link #MODE_RECENT}, otherwise use {@link #MODE_STATIC}.
+ */
+ public static final int MODE_AUTO = 2;
+
public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
@NonNull
@@ -117,20 +150,25 @@
if (result != 0) {
return result;
}
- // Subtype that has the same locale of the system's has higher priority.
- result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
- if (result != 0) {
- return result;
+ if (!Flags.imeSwitcherRevamp()) {
+ // Subtype that has the same locale of the system's has higher priority.
+ result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+ if (result != 0) {
+ return result;
+ }
+ // Subtype that has the same language of the system's has higher priority.
+ result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
+ if (result != 0) {
+ return result;
+ }
+ result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+ if (result != 0) {
+ return result;
+ }
}
- // Subtype that has the same language of the system's has higher priority.
- result = (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
- if (result != 0) {
- return result;
- }
- result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
- if (result != 0) {
- return result;
- }
+ // This will no longer compare by subtype name, however as {@link Collections.sort} is
+ // guaranteed to be a stable sorting, this allows sorting by the IME name (and ID),
+ // while maintaining the order of subtypes (given by each IME) at the IME level.
return mImi.getId().compareTo(other.mImi.getId());
}
@@ -226,6 +264,59 @@
return imList;
}
+ @NonNull
+ private static List<ImeSubtypeListItem> getInputMethodAndSubtypeListForHardwareKeyboard(
+ @NonNull Context context, @NonNull InputMethodSettings settings) {
+ if (!Flags.imeSwitcherRevamp()) {
+ return new ArrayList<>();
+ }
+ final int userId = settings.getUserId();
+ final Context userAwareContext = context.getUserId() == userId
+ ? context
+ : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
+
+ final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
+ if (imis.isEmpty()) {
+ Slog.w(TAG, "Enabled input method list is empty.");
+ return new ArrayList<>();
+ }
+
+ final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
+ final int numImes = imis.size();
+ for (int i = 0; i < numImes; ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (!imi.shouldShowInInputMethodPicker()) {
+ continue;
+ }
+ final var subtypes = settings.getEnabledInputMethodSubtypeList(imi, true);
+ final ArraySet<InputMethodSubtype> enabledSubtypeSet = new ArraySet<>(subtypes);
+ final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager());
+ if (!subtypes.isEmpty()) {
+ final int subtypeCount = imi.getSubtypeCount();
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+ }
+ for (int j = 0; j < subtypeCount; j++) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ if (enabledSubtypeSet.contains(subtype)
+ && subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+ final CharSequence subtypeLabel =
+ subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+ .getDisplayName(userAwareContext, imi.getPackageName(),
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel,
+ subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ }
+ }
+ } else {
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ mSystemLocaleStr));
+ }
+ }
+ return imList;
+ }
+
private static int calculateSubtypeId(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
@@ -385,6 +476,132 @@
}
}
+ /**
+ * List container that allows getting the next item in either forwards or backwards direction,
+ * in either static or recency order, and either in the same IME or not.
+ */
+ private static class RotationList {
+
+ /**
+ * List of items in a static order.
+ */
+ @NonNull
+ private final List<ImeSubtypeListItem> mItems;
+
+ /**
+ * Mapping of recency index to static index (in {@link #mItems}), with lower indices being
+ * more recent.
+ */
+ @NonNull
+ private final int[] mRecencyMap;
+
+ RotationList(@NonNull List<ImeSubtypeListItem> items) {
+ mItems = items;
+ mRecencyMap = new int[items.size()];
+ for (int i = 0; i < mItems.size(); i++) {
+ mRecencyMap[i] = i;
+ }
+ }
+
+ /**
+ * Gets the next input method and subtype from the given ones.
+ *
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param useRecency whether to use the recency order, or the static order.
+ * @param forward whether to search forwards to backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
+ @Nullable
+ public ImeSubtypeListItem next(@NonNull InputMethodInfo imi,
+ @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme,
+ boolean useRecency, boolean forward) {
+ final int size = mItems.size();
+ if (size <= 1) {
+ return null;
+ }
+ final int index = getIndex(imi, subtype, useRecency);
+ if (index < 0) {
+ return null;
+ }
+
+ final int incrementSign = (forward ? 1 : -1);
+
+ for (int i = 1; i < size; i++) {
+ final int nextIndex = (index + i * incrementSign + size) % size;
+ final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex;
+ final var nextItem = mItems.get(mappedIndex);
+ if (!onlyCurrentIme || nextItem.mImi.equals(imi)) {
+ return nextItem;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the given input method and subtype as the most recent one.
+ *
+ * @param imi the input method to set as the most recent.
+ * @param subtype the input method subtype to set as the most recent, if any.
+ * @return {@code true} if the recency was updated, otherwise {@code false}.
+ */
+ public boolean setMostRecent(@NonNull InputMethodInfo imi,
+ @Nullable InputMethodSubtype subtype) {
+ if (mItems.size() <= 1) {
+ return false;
+ }
+
+ final int recencyIndex = getIndex(imi, subtype, true /* useRecency */);
+ if (recencyIndex <= 0) {
+ // Already most recent or not found.
+ return false;
+ }
+ final int staticIndex = mRecencyMap[recencyIndex];
+ System.arraycopy(mRecencyMap, 0, mRecencyMap, 1, recencyIndex);
+ mRecencyMap[0] = staticIndex;
+ return true;
+ }
+
+ /**
+ * Gets the index of the given input method and subtype, in either recency or static order.
+ *
+ * @param imi the input method to get the index of.
+ * @param subtype the input method subtype to get the index of, if any.
+ * @param useRecency whether to get the index in the recency or static order.
+ * @return an index in either {@link #mItems} or {@link #mRecencyMap}, or {@code -1}
+ * if not found.
+ */
+ @IntRange(from = -1)
+ private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ boolean useRecency) {
+ final int subtypeIndex = calculateSubtypeId(imi, subtype);
+ for (int i = 0; i < mItems.size(); i++) {
+ final int mappedIndex = useRecency ? mRecencyMap[i] : i;
+ final var item = mItems.get(mappedIndex);
+ if (item.mImi.equals(imi) && item.mSubtypeId == subtypeIndex) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /** Dumps the state of the list into the given printer. */
+ private void dump(@NonNull Printer pw, @NonNull String prefix) {
+ pw.println(prefix + "Static order:");
+ for (int i = 0; i < mItems.size(); ++i) {
+ final var item = mItems.get(i);
+ pw.println(prefix + "i=" + i + " item=" + item);
+ }
+ pw.println(prefix + "Recency order:");
+ for (int i = 0; i < mRecencyMap.length; ++i) {
+ final int index = mRecencyMap[i];
+ final var item = mItems.get(index);
+ pw.println(prefix + "i=" + i + " item=" + item);
+ }
+ }
+ }
+
@VisibleForTesting
public static class ControllerImpl {
@@ -392,10 +609,23 @@
private final DynamicRotationList mSwitchingAwareRotationList;
@NonNull
private final StaticRotationList mSwitchingUnawareRotationList;
+ /** List of input methods and subtypes. */
+ @Nullable
+ private final RotationList mRotationList;
+ /** List of input methods and subtypes suitable for hardware keyboards. */
+ @Nullable
+ private final RotationList mHardwareRotationList;
+
+ /**
+ * Whether there was a user action since the last input method and subtype switch.
+ * Used to determine the switching behaviour for {@link #MODE_AUTO}.
+ */
+ private boolean mUserActionSinceSwitch;
@NonNull
public static ControllerImpl createFrom(@Nullable ControllerImpl currentInstance,
- @NonNull List<ImeSubtypeListItem> sortedEnabledItems) {
+ @NonNull List<ImeSubtypeListItem> sortedEnabledItems,
+ @NonNull List<ImeSubtypeListItem> hardwareKeyboardItems) {
final var switchingAwareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
true /* supportsSwitchingToNextInputMethod */);
final var switchingUnawareImeSubtypes = filterImeSubtypeList(sortedEnabledItems,
@@ -421,22 +651,55 @@
switchingUnawareRotationList = new StaticRotationList(switchingUnawareImeSubtypes);
}
- return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
+ final RotationList rotationList;
+ if (!Flags.imeSwitcherRevamp()) {
+ rotationList = null;
+ } else if (currentInstance != null && currentInstance.mRotationList != null
+ && Objects.equals(
+ currentInstance.mRotationList.mItems, sortedEnabledItems)) {
+ // Can reuse the current instance.
+ rotationList = currentInstance.mRotationList;
+ } else {
+ rotationList = new RotationList(sortedEnabledItems);
+ }
+
+ final RotationList hardwareRotationList;
+ if (!Flags.imeSwitcherRevamp()) {
+ hardwareRotationList = null;
+ } else if (currentInstance != null && currentInstance.mHardwareRotationList != null
+ && Objects.equals(
+ currentInstance.mHardwareRotationList.mItems, hardwareKeyboardItems)) {
+ // Can reuse the current instance.
+ hardwareRotationList = currentInstance.mHardwareRotationList;
+ } else {
+ hardwareRotationList = new RotationList(hardwareKeyboardItems);
+ }
+
+ return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList,
+ rotationList, hardwareRotationList);
}
private ControllerImpl(@NonNull DynamicRotationList switchingAwareRotationList,
- @NonNull StaticRotationList switchingUnawareRotationList) {
+ @NonNull StaticRotationList switchingUnawareRotationList,
+ @Nullable RotationList rotationList,
+ @Nullable RotationList hardwareRotationList) {
mSwitchingAwareRotationList = switchingAwareRotationList;
mSwitchingUnawareRotationList = switchingUnawareRotationList;
+ mRotationList = rotationList;
+ mHardwareRotationList = hardwareRotationList;
}
@Nullable
public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme,
- @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
if (imi == null) {
return null;
}
- if (imi.supportsSwitchingToNextInputMethod()) {
+ if (Flags.imeSwitcherRevamp() && mRotationList != null) {
+ return mRotationList.next(imi, subtype, onlyCurrentIme,
+ isRecency(mode, forward), forward);
+ } else if (imi.supportsSwitchingToNextInputMethod()) {
return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
subtype);
} else {
@@ -445,11 +708,66 @@
}
}
- public void onUserActionLocked(@NonNull InputMethodInfo imi,
+ @Nullable
+ public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ if (Flags.imeSwitcherRevamp() && mHardwareRotationList != null) {
+ return mHardwareRotationList.next(imi, subtype, onlyCurrentIme,
+ isRecency(mode, forward), forward);
+ }
+ return null;
+ }
+
+ /**
+ * Called when the user took an action that should update the recency of the current
+ * input method and subtype in the switching list.
+ *
+ * @param imi the currently selected input method.
+ * @param subtype the currently selected input method subtype, if any.
+ * @return {@code true} if the recency was updated, otherwise {@code false}.
+ * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+ */
+ public boolean onUserActionLocked(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
- if (imi.supportsSwitchingToNextInputMethod()) {
+ boolean recencyUpdated = false;
+ if (Flags.imeSwitcherRevamp()) {
+ if (mRotationList != null) {
+ recencyUpdated |= mRotationList.setMostRecent(imi, subtype);
+ }
+ if (mHardwareRotationList != null) {
+ recencyUpdated |= mHardwareRotationList.setMostRecent(imi, subtype);
+ }
+ if (recencyUpdated) {
+ mUserActionSinceSwitch = true;
+ }
+ } else if (imi.supportsSwitchingToNextInputMethod()) {
mSwitchingAwareRotationList.onUserAction(imi, subtype);
}
+ return recencyUpdated;
+ }
+
+ /** Called when the input method and subtype was changed. */
+ public void onInputMethodSubtypeChanged() {
+ mUserActionSinceSwitch = false;
+ }
+
+ /**
+ * Whether the given mode and direction result in recency or static order.
+ *
+ * <p>{@link #MODE_AUTO} resolves to the recency order for the first forwards switch
+ * after an {@link #onUserActionLocked user action}, and otherwise to the static order.</p>
+ *
+ * @param mode the switching mode.
+ * @param forward the switching direction.
+ * @return {@code true} for the recency order, otherwise {@code false}.
+ */
+ private boolean isRecency(@SwitchMode int mode, boolean forward) {
+ if (mode == MODE_AUTO && mUserActionSinceSwitch && forward) {
+ return true;
+ } else {
+ return mode == MODE_RECENT;
+ }
}
@NonNull
@@ -473,6 +791,17 @@
mSwitchingAwareRotationList.dump(pw, prefix + " ");
pw.println(prefix + "mSwitchingUnawareRotationList:");
mSwitchingUnawareRotationList.dump(pw, prefix + " ");
+ if (Flags.imeSwitcherRevamp()) {
+ if (mRotationList != null) {
+ pw.println(prefix + "mRotationList:");
+ mRotationList.dump(pw, prefix + " ");
+ }
+ if (mHardwareRotationList != null) {
+ pw.println(prefix + "mHardwareRotationList:");
+ mHardwareRotationList.dump(pw, prefix + " ");
+ }
+ pw.println("User action since last switch: " + mUserActionSinceSwitch);
+ }
}
}
@@ -480,26 +809,71 @@
private ControllerImpl mController;
InputMethodSubtypeSwitchingController() {
- mController = ControllerImpl.createFrom(null, Collections.emptyList());
+ mController = ControllerImpl.createFrom(null, Collections.emptyList(),
+ Collections.emptyList());
}
+ /**
+ * Called when the user took an action that should update the recency of the current
+ * input method and subtype in the switching list.
+ *
+ * @param imi the currently selected input method.
+ * @param subtype the currently selected input method subtype, if any.
+ * @see android.inputmethodservice.InputMethodServiceInternal#notifyUserActionIfNecessary()
+ */
public void onUserActionLocked(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
mController.onUserActionLocked(imi, subtype);
}
+ /** Called when the input method and subtype was changed. */
+ public void onInputMethodSubtypeChanged() {
+ mController.onInputMethodSubtypeChanged();
+ }
+
public void resetCircularListLocked(@NonNull Context context,
@NonNull InputMethodSettings settings) {
mController = ControllerImpl.createFrom(mController,
getSortedInputMethodAndSubtypeList(
false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
- false /* forImeMenu */, context, settings));
+ false /* forImeMenu */, context, settings),
+ getInputMethodAndSubtypeListForHardwareKeyboard(context, settings));
}
+ /**
+ * Gets the next input method and subtype, starting from the given ones, in the given direction.
+ *
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param mode the switching mode.
+ * @param forward whether to search search forwards or backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
@Nullable
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
- @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
- return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
+ @Nullable InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, mode, forward);
+ }
+
+ /**
+ * Gets the next input method and subtype suitable for hardware keyboards, starting from the
+ * given ones, in the given direction.
+ *
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param imi the input method to find the next value from.
+ * @param subtype the input method subtype to find the next value from, if any.
+ * @param mode the switching mode
+ * @param forward whether to search search forwards or backwards in the list.
+ * @return the next input method and subtype if found, otherwise {@code null}.
+ */
+ @Nullable
+ public ImeSubtypeListItem getNextInputMethodForHardware(boolean onlyCurrentIme,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
+ @SwitchMode int mode, boolean forward) {
+ return mController.getNextInputMethodForHardware(onlyCurrentIme, imi, subtype, mode,
+ forward);
}
public void dump(@NonNull Printer pw, @NonNull String prefix) {
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-
-import android.annotation.AnyThread;
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
-import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.PatternMatcher;
-import android.util.Slog;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
- * aware of per-user Runtime Resource Overlay (RRO).
- */
-final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
- private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
-
- private static final String SYSTEM_PACKAGE_NAME = "android";
-
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final AtomicBoolean mValueRef;
- @NonNull
- private final AtomicReference<Runnable> mCleanerRef;
-
- /**
- * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
- * with a value change callback for the user associated with the {@link Context}.
- *
- * @param userContext The {@link Context} to be used to access the resource. This needs to be
- * associated with the right user because the Runtime Resource Overlay (RRO)
- * is per-user configuration.
- * @param boolResId The resource ID to be queried.
- * @param handler {@link Handler} to be used to dispatch {@code callback}.
- * @param callback The callback to be notified when the specified value might be updated.
- * The callback needs to take care of spurious wakeup. The value returned from
- * {@link #get()} may look to be exactly the same as the previously read value
- * e.g. when the value is changed from {@code false} to {@code true} to
- * {@code false} in a very short period of time, because {@link #get()} always
- * does volatile-read.
- * @return New {@link OverlayableSystemBooleanResourceWrapper}.
- */
- @NonNull
- @UserHandleAware
- static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
- @BoolRes int boolResId, @NonNull Handler handler,
- @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
-
- // Note that we cannot fully trust this initial value due to the dead time between obtaining
- // the value here and setting up a broadcast receiver for change callback below.
- // We will refresh the value again later after setting up the change callback anyway.
- final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
-
- final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
-
- final OverlayableSystemBooleanResourceWrapper object =
- new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
- cleanerRef);
-
- final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
- intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
-
- final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean newValue = evaluate(userContext, boolResId);
- if (newValue != valueRef.getAndSet(newValue)) {
- callback.accept(object);
- }
- }
- };
- userContext.registerReceiver(broadcastReceiver, intentFilter,
- null /* broadcastPermission */, handler,
- Context.RECEIVER_NOT_EXPORTED);
- cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
-
- // Make sure that the initial observable value is obtained after the change callback is set.
- valueRef.set(evaluate(userContext, boolResId));
- return object;
- }
-
- private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
- @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
- mUserId = userId;
- mValueRef = valueRef;
- mCleanerRef = cleanerRef;
- }
-
- /**
- * @return The boolean resource value.
- */
- @AnyThread
- boolean get() {
- return mValueRef.get();
- }
-
- /**
- * @return The user ID associated with this resource reader.
- */
- @AnyThread
- @UserIdInt
- int getUserId() {
- return mUserId;
- }
-
- @AnyThread
- private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
- try {
- return context.getPackageManager()
- .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
- .getBoolean(boolResId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
- return false;
- }
- }
-
- /**
- * Cleans up the callback.
- */
- @AnyThread
- @Override
- public void close() {
- final Runnable cleaner = mCleanerRef.getAndSet(null);
- if (cleaner != null) {
- cleaner.run();
- }
- }
-}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index 4764e4f..e7cff20 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -20,10 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -321,30 +318,13 @@
}
/**
- * Called when {@link InputMethodManagerService} is starting.
+ * Called when the system is starting.
*
- * @param context the {@link Context} to be used.
+ * @param contentResolver the {@link ContentResolver} to be used
*/
@AnyThread
- static void onStart(@NonNull Context context) {
- sContentResolver = context.getContentResolver();
-
- final int userId = LocalServices.getService(ActivityManagerInternal.class)
- .getCurrentUserId();
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- putOrGet(userId, createImpl(userManagerInternal, userId));
-
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- synchronized (sUserMap) {
- sUserMap.remove(userId);
- }
- }
- }
- );
+ static void setContentResolver(@NonNull ContentResolver contentResolver) {
+ sContentResolver = contentResolver;
}
/**
@@ -377,6 +357,18 @@
}
/**
+ * Called when a user is being removed.
+ *
+ * @param userId the ID of the user whose storage is being removed.
+ */
+ @AnyThread
+ static void onUserRemoved(@UserIdInt int userId) {
+ synchronized (sUserMap) {
+ sUserMap.remove(userId);
+ }
+ }
+
+ /**
* Put the given string {@code value} to {@code key}.
*
* @param key a secure settings key.
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 59411ad..7c68d54 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -16,11 +16,10 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ImeTracker;
@@ -29,62 +28,64 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.server.pm.UserManagerInternal;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
final class UserDataRepository {
- @GuardedBy("ImfLock.class")
+ private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+
+ @GuardedBy("mUserDataLock")
private final SparseArray<UserData> mUserData = new SparseArray<>();
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
- UserData userData = mUserData.get(userId);
- if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
- mUserData.put(userId, userData);
+ mUserDataLock.writeLock().lock();
+ try {
+ UserData userData = mUserData.get(userId);
+ if (userData == null) {
+ userData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ mUserData.put(userId, userData);
+ }
+ return userData;
+ } finally {
+ mUserDataLock.writeLock().unlock();
}
- return userData;
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
void forAllUserData(Consumer<UserData> consumer) {
- for (int i = 0; i < mUserData.size(); i++) {
- consumer.accept(mUserData.valueAt(i));
+ final SparseArray<UserData> copiedArray;
+ mUserDataLock.readLock().lock();
+ try {
+ copiedArray = mUserData.clone();
+ } finally {
+ mUserDataLock.readLock().unlock();
+ }
+ for (int i = 0; i < copiedArray.size(); i++) {
+ consumer.accept(copiedArray.valueAt(i));
}
}
UserDataRepository(
- @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
@NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
mBindingControllerFactory = bindingControllerFactory;
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- handler.post(() -> {
- synchronized (ImfLock.class) {
- mUserData.remove(userId);
- }
- });
- }
+ }
- @Override
- public void onUserCreated(UserInfo user, Object unusedToken) {
- final int userId = user.id;
- handler.post(() -> {
- synchronized (ImfLock.class) {
- getOrCreate(userId);
- }
- });
- }
- });
+ @AnyThread
+ void remove(@UserIdInt int userId) {
+ mUserDataLock.writeLock().lock();
+ try {
+ mUserData.remove(userId);
+ } finally {
+ mUserDataLock.writeLock().unlock();
+ }
}
/** Placeholder for all IMMS user specific fields */
@@ -181,6 +182,12 @@
String mLastEnabledInputMethodsStr = "";
/**
+ * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+ */
+ @NonNull
+ final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
* Intended to be instantiated only from this file.
*/
private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 41aac32..770e12d 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -296,6 +296,12 @@
return mInner.isInputMethodPickerShownForTest();
}
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void onImeSwitchButtonClickFromSystem(int displayId) {
+ mInner.onImeSwitchButtonClickFromSystem(displayId);
+ }
+
@Override
public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
return mInner.getCurrentInputMethodSubtype(userId);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
index 058c1c8..e1b1416 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -16,10 +16,12 @@
package com.android.server.location.contexthub;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
import java.util.Collection;
+import java.util.Optional;
/**
* A class to log events and useful metrics within the Context Hub service.
@@ -149,10 +151,20 @@
*/
public final NanoAppMessage message;
+ /**
+ * the error code for the message
+ */
+ public Optional<Byte> errorCode;
+
public NanoappMessageEvent(long mTimeStampInMs, int mContextHubId,
NanoAppMessage mMessage, boolean mSuccess) {
super(mTimeStampInMs, mContextHubId, 0, mSuccess);
message = mMessage;
+ errorCode = Optional.empty();
+ }
+
+ public void setErrorCode(byte errorCode) {
+ this.errorCode = Optional.of(errorCode);
}
@Override
@@ -165,6 +177,8 @@
sb.append(message.toString());
sb.append(", success = ");
sb.append(success ? "true" : "false");
+ sb.append(", errorCode = ");
+ sb.append(errorCode.isPresent() ? errorCode.get() : "null");
sb.append(']');
return sb.toString();
}
@@ -312,6 +326,28 @@
}
/**
+ * Logs the status of a reliable message
+ *
+ * @param messageSequenceNumber the message sequence number
+ * @param errorCode the error code
+ */
+ public synchronized void logReliableMessageToNanoappStatus(
+ int messageSequenceNumber, byte errorCode) {
+ if (!Flags.reliableMessage()) {
+ return;
+ }
+
+ for (NanoappMessageEvent event : mMessageToNanoappQueue) {
+ if (event.message.isReliable()
+ && event.message.getMessageSequenceNumber()
+ == messageSequenceNumber) {
+ event.setErrorCode(errorCode);
+ break;
+ }
+ }
+ }
+
+ /**
* Logs a context hub restart event
*
* @param contextHubId the ID of the context hub
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 a0aad52..ed451ff 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1087,6 +1087,8 @@
* @param messageDeliveryStatus The message delivery status to deliver.
*/
private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+ ContextHubEventLogger.getInstance().logReliableMessageToNanoappStatus(
+ messageDeliveryStatus.messageSequenceNumber, messageDeliveryStatus.errorCode);
mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
messageDeliveryStatus.errorCode == ErrorCode.OK);
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index e50324e..f2714db 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -29,19 +29,22 @@
public class ContextHubTestModeManager {
private static final String TAG = "ContextHubTestModeManager";
- /** Probability (in percent) of duplicating a message. */
- private static final int MESSAGE_DROP_PROBABILITY_PERCENT = 20;
+ /** Probability of duplicating a message. */
+ private static final double MESSAGE_DROP_PROBABILITY = 0.05;
- /** Probability (in percent) of duplicating a message. */
- private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 20;
+ /** Probability of duplicating a message. */
+ private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
- /** The number of total messages to send when the duplicate event happens. */
+ /** The number of total messages to send when the duplication event happens. */
private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
- /** A probability percent for a certain event. */
- private static final int MAX_PROBABILITY_PERCENT = 100;
+ /**
+ * The seed for the random number generator. This is used to make the
+ * test more deterministic.
+ */
+ private static final long SEED = 0xDEADBEEF;
- private final Random mRandom = new Random();
+ private final Random mRandom = new Random(SEED);
/**
* @return whether the message was handled
@@ -50,8 +53,7 @@
public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
if (Flags.reliableMessageDuplicateDetectionService()
&& message.isReliable()
- && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
- < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
+ && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
Log.i(TAG, "[TEST MODE] Duplicating message ("
+ NUM_MESSAGES_TO_DUPLICATE
+ " sends) with message sequence number: "
@@ -71,8 +73,7 @@
public boolean sendMessageToContextHub(NanoAppMessage message) {
if (Flags.reliableMessageRetrySupportService()
&& message.isReliable()
- && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
- < MESSAGE_DROP_PROBABILITY_PERCENT) {
+ && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+ message.getMessageSequenceNumber());
return true;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index e3d5c54..803b125 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.media.projection;
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -34,10 +35,12 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AppOpsManager;
import android.app.IProcessObserver;
+import android.app.KeyguardManager;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -78,6 +81,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.wm.WindowManagerInternal;
@@ -132,6 +136,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final PackageManager mPackageManager;
private final WindowManagerInternal mWmInternal;
+ private final KeyguardManager mKeyguardManager;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -147,7 +152,9 @@
this(context, new Injector());
}
- @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) {
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ @VisibleForTesting
+ MediaProjectionManagerService(Context context, Injector injector) {
super(context);
mContext = context;
mInjector = injector;
@@ -163,9 +170,47 @@
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mKeyguardManager.addKeyguardLockedStateListener(
+ mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
Watchdog.getInstance().addMonitor(this);
}
+ /**
+ * In order to record the keyguard, the MediaProjection package must be either:
+ * - a holder of RECORD_SENSITIVE_CONTENT permission, or
+ * - be one of the bugreport whitelisted packages
+ */
+ private boolean canCaptureKeyguard() {
+ if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+ return true;
+ }
+ synchronized (mLock) {
+ if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
+ return false;
+ }
+ if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
+ mProjectionGrant.packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ return SystemConfig.getInstance().getBugreportWhitelistedPackages()
+ .contains(mProjectionGrant.packageName);
+ }
+ }
+
+ @VisibleForTesting
+ void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+ if (!isKeyguardLocked) return;
+ synchronized (mLock) {
+ if (mProjectionGrant != null && !canCaptureKeyguard()) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection"
+ + " due to keyguard lock");
+ mProjectionGrant.stop();
+ }
+ }
+ }
+
/** Functional interface for providing time. */
@VisibleForTesting
interface Clock {
@@ -1252,6 +1297,11 @@
@Override
public void notifyVirtualDisplayCreated(int displayId) {
notifyVirtualDisplayCreated_enforcePermission();
+ if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
+ Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
+ stop();
+ return;
+ }
synchronized (mLock) {
mVirtualDisplayId = displayId;
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index ec5d96d..4a82057 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -106,11 +106,11 @@
/**
* Potentially log a zen mode change if the provided config and policy changes warrant it.
*
- * @param prevInfo ZenModeInfo (zen mode setting, config, policy) prior to this change
- * @param newInfo ZenModeInfo after this change takes effect
- * @param callingUid the calling UID associated with the change; may be used to attribute the
- * change to a particular package or determine if this is a user action
- * @param origin The origin of the Zen change.
+ * @param prevInfo ZenModeInfo (zen mode setting, config, policy) prior to this change
+ * @param newInfo ZenModeInfo after this change takes effect
+ * @param callingUid the calling UID associated with the change; may be used to attribute the
+ * change to a particular package or determine if this is a user action
+ * @param origin The origin of the Zen change.
*/
public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
@ConfigChangeOrigin int origin) {
@@ -127,6 +127,9 @@
/**
* Reassign callingUid in mChangeState if we have more specific information that warrants it
* (for instance, if the change is automatic and due to an automatic rule change).
+ *
+ * <p>When Flags.modesUi() is enabled, we reassign the calling UID to the package UID in all
+ * changes whose source is not system or system UI, as long as there is only one rule changed.
*/
private void maybeReassignCallingUid() {
int userId = Process.INVALID_UID;
@@ -145,12 +148,23 @@
userId = mChangeState.mNewConfig.user; // mNewConfig must not be null if enabler exists
}
- // The conditions where we should consider reassigning UID for an automatic rule change:
+ // The conditions where we should consider reassigning UID for an automatic rule change
+ // (pre-modes_ui):
// - we've determined it's not a user action
// - our current best guess is that the calling uid is system/sysui
+ // When Flags.modesUi() is true, we get the package UID for the changed rule, as long as:
+ // - the change does not originate from the system based on change origin
+ // - there is only one rule changed
if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) {
- if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
- return;
+ if (Flags.modesUi()) {
+ // ignore anything whose origin is system
+ if (mChangeState.isFromSystemOrSystemUi()) {
+ return;
+ }
+ } else {
+ if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
+ return;
+ }
}
// Only try to get the package UID if there's exactly one changed automatic rule. If
@@ -202,7 +216,8 @@
/* int32 package_uid = 7 */ mChangeState.getPackageUid(),
/* DNDPolicyProto current_policy = 8 */ mChangeState.getDNDPolicyProto(),
/* bool are_channels_bypassing = 9 */ mChangeState.getAreChannelsBypassing(),
- /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes());
+ /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes(),
+ /* ChangeOrigin change_origin = 11 */ mChangeState.getChangeOrigin());
}
/**
@@ -235,7 +250,8 @@
ZenModeConfig mPrevConfig, mNewConfig;
NotificationManager.Policy mPrevPolicy, mNewPolicy;
int mCallingUid = Process.INVALID_UID;
- @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+ @ConfigChangeOrigin
+ int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
@ConfigChangeOrigin int origin) {
@@ -388,7 +404,8 @@
* rules available.
*/
@SuppressLint("WrongConstant") // special case for log-only type on manual rule
- @NonNull List<ZenRule> activeRulesList(ZenModeConfig config) {
+ @NonNull
+ List<ZenRule> activeRulesList(ZenModeConfig config) {
ArrayList<ZenRule> rules = new ArrayList<>();
if (config == null) {
return rules;
@@ -548,6 +565,17 @@
}
/**
+ * Get the config change origin associated with this change, which is stored in mOrigin.
+ * Only useable if modes_ui is true.
+ */
+ int getChangeOrigin() {
+ if (Flags.modesUi()) {
+ return mOrigin;
+ }
+ return 0;
+ }
+
+ /**
* Convert the new policy to a DNDPolicyProto format for output in logs.
*
* <p>If {@code mNewZenMode} is {@code ZEN_MODE_OFF} (which can mean either no rules
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
private final OverlayActorEnforcer mActorEnforcer;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index c5e2bb8..8410cff 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -34,6 +34,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -370,8 +371,13 @@
}
private void enforceUid(int callingUid) {
- if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) {
- throw new SecurityException("uid " + callingUid + " not allowed to access PDB");
+ enforceUid(callingUid, /* allowShell= */ false);
+ }
+
+ private void enforceUid(int callingUid, boolean allowShell) {
+ if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT
+ && (callingUid != Process.SHELL_UID || !allowShell)) {
+ throw new SecurityException("Uid " + callingUid + " not allowed to access PDB");
}
}
@@ -864,7 +870,8 @@
private final IBinder mService = new IPersistentDataBlockService.Stub() {
private int printFrpStatus(PrintWriter pw, boolean printSecrets) {
- enforceUid(Binder.getCallingUid());
+ // Only allow SHELL_UID to print the status if printing the secrets is disabled
+ enforceUid(Binder.getCallingUid(), /* allowShell= */ !printSecrets);
pw.println("FRP state");
pw.println("=========");
@@ -872,8 +879,14 @@
pw.println("FRP state: " + mFrpActive);
printFrpDataFilesContents(pw, printSecrets);
printFrpSecret(pw, printSecrets);
- pw.println("OEM unlock state: " + getOemUnlockEnabled());
- pw.println("Bootloader lock state: " + getFlashLockState());
+
+ // Do not print OEM unlock state and flash lock state if the caller is a non-root
+ // shell - it likely won't have permissions anyways.
+ if (Binder.getCallingUid() != Process.SHELL_UID) {
+ pw.println("OEM unlock state: " + getOemUnlockEnabled());
+ pw.println("Bootloader lock state: " + getFlashLockState());
+ }
+
pw.println("Verified boot state: " + getVerifiedBootState());
pw.println("Has FRP credential handle: " + hasFrpCredentialHandle());
pw.println("FRP challenge block size: " + getDataBlockSize());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 009e9b8..303371b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -801,8 +801,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
- null /* requiredPermission */, options.toBundle());
+ target.sendIntent(context, 0, fillIn,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -2628,18 +2629,28 @@
String packageName = pkgLite.packageName;
synchronized (mPm.mLock) {
- // Package which currently owns the data that the new package will own if installed.
- // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
- // will be null whereas dataOwnerPkg will contain information about the package
- // which was uninstalled while keeping its data.
- AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
- if (dataOwnerPkg == null) {
- if (dataOwnerPs != null) {
- dataOwnerPkg = dataOwnerPs.getPkg();
+ if (dataOwnerPs == null) {
+ if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
+ String errorMsg = "Required installed version code was "
+ + requiredInstalledVersionCode
+ + " but package is not installed";
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
}
+ // The package doesn't exist in the system, don't need to check the version
+ // replacing.
+ return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
}
+ // Package which currently owns the data that the new package will own if installed.
+ // If an app is uninstalled while keeping data (e.g. adb uninstall -k), dataOwnerPkg
+ // will be null whereas dataOwnerPs will contain information about the package
+ // which was uninstalled while keeping its data. The AndroidPackage object that the
+ // PackageSetting refers to is the same object that is stored in mPackages.
+ AndroidPackage dataOwnerPkg = dataOwnerPs.getPkg();
+
if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
if (dataOwnerPkg == null) {
String errorMsg = "Required installed version code was "
@@ -2661,7 +2672,27 @@
}
}
- if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
+ // If dataOwnerPkg is null but dataOwnerPs is not null, there is always data on
+ // some users. Wwe should do the downgrade check. E.g. DELETE_KEEP_DATA and
+ // archived apps
+ if (dataOwnerPkg == null) {
+ if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
+ dataOwnerPs.isDebuggable())) {
+ // The data exists on some users and downgrade is not permitted; a lower
+ // version of the app will not be allowed.
+ try {
+ PackageManagerServiceUtils.checkDowngrade(dataOwnerPs, pkgLite);
+ } catch (PackageManagerException e) {
+ String errorMsg = "Downgrade detected on app uninstalled with"
+ + " DELETE_KEEP_DATA: " + e.getMessage();
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ }
+ }
+ // dataOwnerPs.getPkg() is not null on system apps case. Don't need to consider
+ // system apps case like below.
+ } else if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
dataOwnerPkg.isDebuggable())) {
// Downgrade is not permitted; a lower version of the app will not be allowed
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 0d1095f..6b7b702 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -1194,8 +1194,9 @@
MODE_BACKGROUND_ACTIVITY_START_DENIED);
for (IntentSender intentSender : unarchiveIntentSenders) {
try {
- intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
- /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+ intentSender.sendIntent(mContext, 0, broadcastIntent,
+ /* requiredPermission */ null, options.toBundle(),
+ /* executor */ null, /* onFinished */ null);
} catch (IntentSender.SendIntentException e) {
Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
} finally {
@@ -1328,8 +1329,9 @@
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_DENIED);
- statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null,
- /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+ statusReceiver.sendIntent(mContext, 0, intent,
+ /* requiredPermission */ null, options.toBundle(),
+ /* executor */ null, /* onFinished */ null);
} catch (IntentSender.SendIntentException e) {
Slog.e(
TAG,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b93dcdc..f615ca1d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1581,8 +1581,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- callback.sendIntent(mContext, 0, intent, null /* onFinished*/,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ callback.sendIntent(mContext, 0, intent,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (SendIntentException ignore) {
}
});
@@ -1912,8 +1913,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ mTarget.sendIntent(mContext, 0, fillIn,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (SendIntentException ignored) {
}
}
@@ -1945,8 +1947,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ mTarget.sendIntent(mContext, 0, fillIn,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 47a79a3..ff8a69d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -5053,8 +5053,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ target.sendIntent(mContext, 0 /* code */, intent,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -5447,8 +5448,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- target.sendIntent(context, 0, fillIn, null /* onFinished */,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ target.sendIntent(context, 0, fillIn,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -5496,8 +5498,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- target.sendIntent(context, 0, fillIn, null /* onFinished */,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ target.sendIntent(context, 0, fillIn,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -5533,8 +5536,9 @@
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
- target.sendIntent(context, 0, intent, null /* onFinished */,
- null /* handler */, null /* requiredPermission */, options.toBundle());
+ target.sendIntent(context, 0, intent,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (IntentSender.SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2e63cdb..07c8ee7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,7 +186,6 @@
import com.android.internal.pm.pkg.component.ParsedMainComponent;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.telephony.CarrierAppUtils;
-import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -4493,7 +4492,7 @@
void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
boolean hidden) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -4518,8 +4517,7 @@
boolean setSystemAppInstallState(@NonNull Computer snapshot, String packageName,
boolean installed, int userId) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
- || callingUid == Process.SYSTEM_UID;
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -5034,8 +5032,8 @@
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
pi.sendIntent(null, success ? 1 : 0, null /* intent */,
- null /* onFinished*/, null /* handler */,
- null /* requiredPermission */, options.toBundle());
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (SendIntentException e) {
Slog.w(TAG, e);
}
@@ -8123,4 +8121,9 @@
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/);
}
+
+ private static boolean isSystemOrPhone(int uid) {
+ return UserHandle.isSameApp(uid, Process.SYSTEM_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 924b36c..c3cac20 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1419,10 +1419,23 @@
/**
* Check and throw if the given before/after packages would be considered a
- * downgrade.
+ * downgrade with {@link PackageSetting}.
*/
- public static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
- throws PackageManagerException {
+ public static void checkDowngrade(@NonNull PackageSetting before,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
+ if (after.getLongVersionCode() < before.getVersionCode()) {
+ throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+ "Update version code " + after.versionCode + " is older than current "
+ + before.getVersionCode());
+ }
+ }
+
+ /**
+ * Check and throw if the given before/after packages would be considered a
+ * downgrade with {@link AndroidPackage}.
+ */
+ public static void checkDowngrade(@NonNull AndroidPackage before,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
if (after.getLongVersionCode() < before.getLongVersionCode()) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update version code " + after.versionCode + " is older than current "
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7870b17..82df527 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,6 +97,7 @@
FORCE_QUERYABLE_OVERRIDE,
SCANNED_AS_STOPPED_SYSTEM_APP,
PENDING_RESTORE,
+ DEBUGGABLE,
})
public @interface Flags {
}
@@ -105,6 +106,7 @@
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
private static final int PENDING_RESTORE = 1 << 4;
+ private static final int DEBUGGABLE = 1 << 5;
}
private int mBooleans;
@@ -562,6 +564,20 @@
return getBoolean(Booleans.PENDING_RESTORE);
}
+ /**
+ * @see PackageState#isDebuggable
+ */
+ public PackageSetting setDebuggable(boolean value) {
+ setBoolean(Booleans.DEBUGGABLE, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return getBoolean(Booleans.DEBUGGABLE);
+ }
+
@Override
public String toString() {
return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 9ab6016..d8ce38e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,6 +437,9 @@
pkgSetting.setIsOrphaned(true);
}
+ // update debuggable to packageSetting
+ pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
final long existingFirstInstallTime = userId == UserHandle.USER_ALL
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3956552..0d16b00 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3255,6 +3255,9 @@
if (pkg.isPendingRestore()) {
serializer.attributeBoolean(null, "pendingRestore", true);
}
+ if (pkg.isDebuggable()) {
+ serializer.attributeBoolean(null, "debuggable", true);
+ }
if (pkg.isLoading()) {
serializer.attributeBoolean(null, "isLoading", true);
}
@@ -3269,7 +3272,6 @@
serializer.attributeInt(null, "appMetadataSource",
pkg.getAppMetadataSource());
-
writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
@@ -4059,6 +4061,7 @@
long versionCode = 0;
boolean installedForceQueryable = false;
boolean isPendingRestore = false;
+ boolean isDebuggable = false;
float loadingProgress = 0;
long loadingCompletedTime = 0;
UUID domainSetId;
@@ -4085,6 +4088,7 @@
updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
isPendingRestore = parser.getAttributeBoolean(null, "pendingRestore", false);
+ isDebuggable = parser.getAttributeBoolean(null, "debuggable", false);
loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0);
@@ -4259,6 +4263,7 @@
.setUpdateAvailable(updateAvailable)
.setForceQueryableOverride(installedForceQueryable)
.setPendingRestore(isPendingRestore)
+ .setDebuggable(isDebuggable)
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
.setAppMetadataFilePath(appMetadataFilePath)
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4a60e45..021f7aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4490,8 +4490,9 @@
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_DENIED);
- intentSender.sendIntent(mContext, /* code= */ 0, extras,
- /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
+ intentSender.sendIntent(mContext, 0 /* code */, extras,
+ null /* requiredPermission */, options.toBundle(),
+ null /* executor */, null /* onFinished*/);
} catch (SendIntentException e) {
Slog.w(TAG, "sendIntent failed().", e);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b9a9d64f..dde9943 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1176,6 +1176,30 @@
}
}
+ /** Marks the user as slated for deletion during boot if necessary. **/
+ @GuardedBy("mUsersLock")
+ private void markUserForRemovalIfNecessaryLU(UserInfo ui) {
+ if (!ui.isEphemeral()) {
+ // User should be ephemeral to be marked for removal.
+ return;
+ }
+ if (ui.preCreated) {
+ // Avoid marking pre-created users for removal.
+ return;
+ }
+ if (ui.lastLoggedInTime == 0 && ui.isGuest() && Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_guestUserAutoCreated)) {
+ // Avoid marking auto-created but not-yet-logged-in guest user for removal. Because a
+ // new one will be created anyway, and this one doesn't have any personal data in it yet
+ // due to not being logged in.
+ return;
+ }
+ // Mark the user for removal.
+ addRemovingUserIdLocked(ui.id);
+ ui.partial = true;
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ }
+
/* Prunes out any partially created or partially removed users. */
private void cleanupPartialUsers() {
ArrayList<UserInfo> partials = new ArrayList<>();
@@ -1901,6 +1925,7 @@
Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
return;
}
+ UserManager.invalidateQuietModeEnabledCache();
profile.flags ^= UserInfo.FLAG_QUIET_MODE;
profileUserData = getUserDataLU(profile.id);
}
@@ -4340,13 +4365,7 @@
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
}
- if (userData.info.isEphemeral() && !userData.info.preCreated
- && userData.info.id != UserHandle.USER_SYSTEM) {
- // Mark ephemeral user as slated for deletion.
- addRemovingUserIdLocked(userData.info.id);
- userData.info.partial = true;
- userData.info.flags |= UserInfo.FLAG_DISABLED;
- }
+ markUserForRemovalIfNecessaryLU(userData.info);
}
}
} else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e0ee199..5876188 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -274,6 +274,14 @@
boolean isPendingRestore();
/**
+ * @see ApplicationInfo#FLAG_DEBUGGABLE
+ * @see R.styleable#AndroidManifestApplication_debuggable
+ * @see AndroidPackage#isDebuggable
+ * @hide
+ */
+ boolean isDebuggable();
+
+ /**
* Retrieves the shared user app ID. Note that the actual shared user data is not available here
* and must be queried separately.
*
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 3a79d0d..fde23b7 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -22,8 +22,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.RemoteException;
@@ -36,7 +38,11 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
import com.android.server.input.KeyboardMetricsCollector;
@@ -46,7 +52,9 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -183,8 +191,12 @@
String rolePackage = mRoleManager.getDefaultApplication(role);
if (rolePackage != null) {
intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
- intent.putExtra(EXTRA_ROLE, role);
- mRoleIntents.put(role, intent);
+ if (intent != null) {
+ intent.putExtra(EXTRA_ROLE, role);
+ mRoleIntents.put(role, intent);
+ } else {
+ Log.w(TAG, "No launch intent for role " + role);
+ }
} else {
Log.w(TAG, "No default application for role " + role);
}
@@ -198,8 +210,7 @@
private void loadShortcuts() {
try {
- XmlResourceParser parser = mContext.getResources().getXml(
- com.android.internal.R.xml.bookmarks);
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
while (true) {
@@ -270,6 +281,9 @@
continue;
}
intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ if (intent == null) {
+ Log.w(TAG, "Null selector intent for " + categoryName);
+ }
} else if (roleName != null) {
// We can't resolve the role at the time of this file being parsed as the
// device hasn't finished booting, so we will look it up lazily.
@@ -466,4 +480,131 @@
return false;
}
+
+ /**
+ * @param deviceId The input device id of the input device that will handle the shortcuts.
+ *
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts parsed at boot time from {@code bookmarks.xml}.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList();
+ for (int i = 0; i < mIntentShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
+ /**
+ * Given an intent to launch an application and the character and shift state that should
+ * trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
+ * icon for the target application.
+ *
+ * @param baseChar the character that triggers the shortcut
+ * @param intent the application launch intent
+ * @param shift whether the shift key is required to be presed.
+ */
+ @VisibleForTesting
+ KeyboardShortcutInfo shortcutInfoFromIntent(char baseChar, Intent intent, boolean shift) {
+ if (intent == null) {
+ return null;
+ }
+
+ CharSequence label;
+ Icon icon;
+ ActivityInfo resolvedActivity = intent.resolveActivityInfo(
+ mPackageManager, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolvedActivity == null) {
+ return null;
+ }
+ boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals(
+ resolvedActivity.name);
+ if (isResolver) {
+ label = getIntentCategoryLabel(mContext,
+ intent.getSelector().getCategories().iterator().next());
+ if (label == null) {
+ return null;
+ }
+ icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon);
+
+ } else {
+ label = resolvedActivity.loadLabel(mPackageManager);
+ icon = Icon.createWithResource(
+ resolvedActivity.packageName, resolvedActivity.getIconResource());
+ }
+ int modifiers = KeyEvent.META_META_ON;
+ if (shift) {
+ modifiers |= KeyEvent.META_SHIFT_ON;
+ }
+ return new KeyboardShortcutInfo(label, icon, baseChar, modifiers);
+ }
+
+ @VisibleForTesting
+ static String getIntentCategoryLabel(Context context, CharSequence category) {
+ int resid;
+ switch (category.toString()) {
+ case Intent.CATEGORY_APP_BROWSER:
+ resid = R.string.keyboard_shortcut_group_applications_browser;
+ break;
+ case Intent.CATEGORY_APP_CONTACTS:
+ resid = R.string.keyboard_shortcut_group_applications_contacts;
+ break;
+ case Intent.CATEGORY_APP_EMAIL:
+ resid = R.string.keyboard_shortcut_group_applications_email;
+ break;
+ case Intent.CATEGORY_APP_CALENDAR:
+ resid = R.string.keyboard_shortcut_group_applications_calendar;
+ break;
+ case Intent.CATEGORY_APP_MAPS:
+ resid = R.string.keyboard_shortcut_group_applications_maps;
+ break;
+ case Intent.CATEGORY_APP_MUSIC:
+ resid = R.string.keyboard_shortcut_group_applications_music;
+ break;
+ case Intent.CATEGORY_APP_MESSAGING:
+ resid = R.string.keyboard_shortcut_group_applications_sms;
+ break;
+ case Intent.CATEGORY_APP_CALCULATOR:
+ resid = R.string.keyboard_shortcut_group_applications_calculator;
+ break;
+ default:
+ Log.e(TAG, ("No label for app category " + category));
+ return null;
+ }
+ return context.getString(resid);
+ };
+
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9d0c0e9..8dc9756 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -189,6 +189,7 @@
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.FallbackAction;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -3321,6 +3322,11 @@
eventToLog).sendToTarget();
}
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
// TODO(b/117479243): handle it in InputPolicy
// TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring
/** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9ca4e27..6c05d70 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -82,6 +82,7 @@
import android.view.Display;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants;
@@ -698,6 +699,15 @@
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
/**
+ * Return the set of applicaition launch keyboard shortcuts the system supports.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will trigger the shortcut.
+ *
+ * @return {@link KeyboardShortcutGroup} containing the shortcuts.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Called from the input reader thread before a motion is enqueued when the device is in a
* non-interactive state.
*
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index fa94b43..421471e 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1380,10 +1380,7 @@
Slog.d(TAG, "notifyStandbyPortsChanged");
}
- final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+ sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
}
/**
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index f85b8cc..aa5f5a24 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -206,7 +206,6 @@
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
mScreenUndimDetector = screenUndimDetector;
- mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, null);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -214,6 +213,7 @@
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mTrustManager = mContext.getSystemService(TrustManager.class);
mVibrator = mContext.getSystemService(Vibrator.class);
+ mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, null);
mHandler = new NotifierHandler(looper);
mBackgroundExecutor = backgroundExecutor;
@@ -813,6 +813,8 @@
if (DEBUG) {
Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
}
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ SystemClock.uptimeMillis(), displayGroupId, newPolicy);
synchronized (mLock) {
Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ce0120c..6fe1ccd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -33,6 +33,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle;
+import static com.android.server.display.DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -239,9 +240,6 @@
// This should perhaps be a setting.
private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000;
- // Float.NaN cannot be stored in config.xml so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
@@ -619,7 +617,6 @@
public final float mScreenBrightnessMinimum;
public final float mScreenBrightnessMaximum;
public final float mScreenBrightnessDefault;
- public final float mScreenBrightnessDoze;
public final float mScreenBrightnessDim;
// Value we store for tracking face down behavior.
@@ -1219,8 +1216,6 @@
.config_screenBrightnessSettingMaximumFloat);
final float def = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingDefaultFloat);
- final float doze = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessDozeFloat);
final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessDimFloat);
@@ -1240,13 +1235,6 @@
mScreenBrightnessMaximum = max;
mScreenBrightnessDefault = def;
}
- if (doze == INVALID_BRIGHTNESS_IN_CONFIG) {
- mScreenBrightnessDoze = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessDoze));
- } else {
- mScreenBrightnessDoze = doze;
- }
if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
mScreenBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
mContext.getResources().getInteger(com.android.internal.R.integer
@@ -6090,8 +6078,6 @@
return mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
return mScreenBrightnessDim;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
- return mScreenBrightnessDoze;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
diff --git a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
index d57cd5d..3546565 100644
--- a/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
+++ b/services/core/java/com/android/server/power/WakefulnessSessionObserver.java
@@ -16,8 +16,11 @@
package com.android.server.power;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
import static android.os.PowerManagerInternal.isInteractive;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.power.PowerManagerService.DEFAULT_SCREEN_OFF_TIMEOUT;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_NON_INTERACTIVE;
@@ -34,6 +37,9 @@
import android.app.SynchronousUserSwitchObserver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
@@ -44,9 +50,13 @@
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -117,9 +127,42 @@
@Retention(RetentionPolicy.SOURCE)
private @interface OverrideOutcome {}
- private static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
- private static final long TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
+ private static final int POLICY_REASON_UNKNOWN = FrameworkStatsLog
+ .SCREEN_DIM_REPORTED__POLICY_REASON__UNKNOWN;
+ @VisibleForTesting
+ protected static final int POLICY_REASON_OFF_TIMEOUT = FrameworkStatsLog
+ .SCREEN_DIM_REPORTED__POLICY_REASON__OFF_TIMEOUT;
+ @VisibleForTesting
+ protected static final int POLICY_REASON_OFF_POWER_BUTTON = FrameworkStatsLog
+ .SCREEN_DIM_REPORTED__POLICY_REASON__OFF_POWER_BUTTON;
+ @VisibleForTesting
+ protected static final int POLICY_REASON_BRIGHT_UNDIM = FrameworkStatsLog
+ .SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_UNDIM;
+ @VisibleForTesting
+ protected static final int POLICY_REASON_BRIGHT_INITIATED_REVERT = FrameworkStatsLog
+ .SCREEN_DIM_REPORTED__POLICY_REASON__BRIGHT_INITIATED_REVERT;
+
+ /**
+ * Policy Reason
+ * {@link android.os.statsd.power.ScreenDimReported.PolicyReason}.
+ */
+ @IntDef(prefix = {"POLICY_REASON_"}, value = {
+ POLICY_REASON_UNKNOWN,
+ POLICY_REASON_OFF_TIMEOUT,
+ POLICY_REASON_OFF_POWER_BUTTON,
+ POLICY_REASON_BRIGHT_UNDIM,
+ POLICY_REASON_BRIGHT_INITIATED_REVERT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PolicyReason {}
+
+ @VisibleForTesting protected static final int DEFAULT_USER_ACTIVITY = USER_ACTIVITY_EVENT_OTHER;
+ private static final long USER_INITIATED_REVERT_THRESHOLD_MILLIS = 5000L;
private static final long SEND_OVERRIDE_TIMEOUT_LOG_THRESHOLD_MILLIS = 1000L;
+ @VisibleForTesting
+ protected static final long SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS = 500L;
+
+ @VisibleForTesting protected static final Object HANDLER_TOKEN = new Object();
private Context mContext;
private int mScreenOffTimeoutMs;
@@ -130,17 +173,24 @@
protected WakefulnessSessionFrameworkStatsLogger mWakefulnessSessionFrameworkStatsLogger;
private final Clock mClock;
private final Object mLock = new Object();
+ private final Handler mHandler;
- public WakefulnessSessionObserver(Context context, Injector injector) {
+ private DisplayManagerInternal mDisplayManagerInternal;
+ private int mPhysicalDisplayPortIdForDefaultDisplay;
+
+ public WakefulnessSessionObserver(
+ Context context, Injector injector) {
if (injector == null) {
injector = new Injector();
}
mContext = context;
+ mDisplayManagerInternal = injector.getDisplayManagerInternal();
mWakefulnessSessionFrameworkStatsLogger = injector
.getWakefulnessSessionFrameworkStatsLogger();
mClock = injector.getClock();
- updateSettingScreenOffTimeout(context);
+ mHandler = injector.getHandler();
+ updateSettingScreenOffTimeout(mContext);
try {
final UserSwitchObserver observer = new UserSwitchObserver();
@@ -164,6 +214,31 @@
},
UserHandle.USER_ALL);
+ mPhysicalDisplayPortIdForDefaultDisplay = getPhysicalDisplayPortId(DEFAULT_DISPLAY);
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ if (displayManager != null) {
+ displayManager.registerDisplayListener(
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ mPhysicalDisplayPortIdForDefaultDisplay = getPhysicalDisplayPortId(
+ DEFAULT_DISPLAY);
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int i) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int i) {
+ }
+ },
+ mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ }
+
mPowerGroups.append(
Display.DEFAULT_DISPLAY_GROUP,
new WakefulnessSessionPowerGroup(Display.DEFAULT_DISPLAY_GROUP));
@@ -186,6 +261,20 @@
}
/**
+ * Track the screen policy
+ *
+ * @param eventTime policy changing time, in uptime millis.
+ * @param powerGroupId Power Group Id for this screen policy
+ * @param newPolicy Screen Policy defined in {@link DisplayPowerRequest}
+ */
+ public void onScreenPolicyUpdate(long eventTime, int powerGroupId, int newPolicy) {
+ if (!mPowerGroups.contains(powerGroupId)) {
+ mPowerGroups.append(powerGroupId, new WakefulnessSessionPowerGroup(powerGroupId));
+ }
+ mPowerGroups.get(powerGroupId).onScreenPolicyUpdate(eventTime, newPolicy);
+ }
+
+ /**
* Track the system wakefulness
*
* @param powerGroupId Power Group Id for this wakefulness changes
@@ -267,6 +356,14 @@
}
}
+ private int getPhysicalDisplayPortId(int displayId) {
+ if (mDisplayManagerInternal == null) {
+ return -1;
+ }
+ DisplayInfo display = mDisplayManagerInternal.getDisplayInfo(displayId);
+ return ((DisplayAddress.Physical) display.address).getPort();
+ }
+
private int getScreenOffTimeout() {
synchronized (mLock) {
return mScreenOffTimeoutMs;
@@ -277,10 +374,9 @@
@VisibleForTesting
protected class WakefulnessSessionPowerGroup {
private static final long TIMEOUT_OFF_RESET_TIMESTAMP = -1;
-
private int mPowerGroupId;
private int mCurrentWakefulness;
- private boolean mIsInteractive = false;
+ @VisibleForTesting protected boolean mIsInteractive = false;
// state on start timestamp: will be used in state off to calculate the duration of state on
private long mInteractiveStateOnStartTimestamp;
@VisibleForTesting
@@ -295,6 +391,17 @@
private int mTimeoutOverrideWakeLockCounter = 0;
// The timestamp when Override Timeout is set to false
private @ScreenTimeoutOverridePolicy.ReleaseReason int mTimeoutOverrideReleaseReason;
+ // The timestamp when current screen policy is set
+ private long mCurrentScreenPolicyTimestamp;
+ // current screen policy
+ private int mCurrentScreenPolicy;
+ // The screen policy before the current one
+ private int mPrevScreenPolicy;
+ // The previous screen policy duration
+ private int mPrevScreenPolicyDurationMs;
+ // The past dim duration
+ @VisibleForTesting protected int mPastDimDurationMs;
+ private long mInteractiveOffTimestamp;
// The timestamp when state off by timeout occurs
// will set TIMEOUT_OFF_RESET_TIMESTAMP if state on or state off by power button
private long mTimeoutOffTimestamp;
@@ -307,6 +414,10 @@
mPrevUserActivityEvent = DEFAULT_USER_ACTIVITY;
mPrevUserActivityTimestamp = -1;
mPowerGroupId = powerGroupId;
+ mCurrentScreenPolicy = mPrevScreenPolicy = POLICY_BRIGHT;
+ mCurrentScreenPolicyTimestamp = 0;
+ mPrevScreenPolicyDurationMs = 0;
+ mPastDimDurationMs = 0;
}
public void notifyUserActivity(long eventTime, @PowerManager.UserActivityEvent int event) {
@@ -320,6 +431,21 @@
mCurrentUserActivityTimestamp = eventTime;
}
+ public void onScreenPolicyUpdate(long eventTime, int newPolicy) {
+ if (newPolicy == mCurrentScreenPolicy) {
+ return;
+ }
+
+ if (newPolicy == POLICY_BRIGHT) {
+ checkAndLogDimIfQualified(POLICY_REASON_BRIGHT_UNDIM, eventTime);
+ }
+
+ mPrevScreenPolicy = mCurrentScreenPolicy;
+ mCurrentScreenPolicy = newPolicy;
+ mPrevScreenPolicyDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+ mCurrentScreenPolicyTimestamp = eventTime;
+ }
+
public void onWakefulnessChangeStarted(int wakefulness, int changeReason, long eventTime) {
mCurrentWakefulness = wakefulness;
if (mIsInteractive == isInteractive(wakefulness)) {
@@ -331,10 +457,10 @@
mInteractiveStateOnStartTimestamp = eventTime;
// Log the outcome of screen timeout override (USER INITIATED REVERT),
- // when user initiates to revert the screen off state in a short period.
+ // when user initiates to revert the off state in a short period.
if (mTimeoutOffTimestamp != TIMEOUT_OFF_RESET_TIMESTAMP) {
- long offToOnDurationMs = eventTime - mTimeoutOffTimestamp;
- if (offToOnDurationMs < TIMEOUT_USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
+ long timeoutOffToOnDurationMs = eventTime - mTimeoutOffTimestamp;
+ if (timeoutOffToOnDurationMs < USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
mWakefulnessSessionFrameworkStatsLogger.logTimeoutOverrideEvent(
mPowerGroupId,
OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT,
@@ -344,11 +470,15 @@
}
mTimeoutOffTimestamp = TIMEOUT_OFF_RESET_TIMESTAMP;
}
+
+ checkAndLogDimIfQualified(POLICY_REASON_BRIGHT_INITIATED_REVERT, eventTime);
+
} else {
int lastUserActivity = mCurrentUserActivityEvent;
long lastUserActivityDurationMs = eventTime - mCurrentUserActivityTimestamp;
@OffReason int interactiveStateOffReason = OFF_REASON_UNKNOWN;
int reducedInteractiveStateOnDurationMs = 0;
+ mInteractiveOffTimestamp = eventTime;
if (changeReason == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) {
interactiveStateOffReason = OFF_REASON_POWER_BUTTON;
@@ -369,6 +499,9 @@
mSendOverrideTimeoutLogTimestamp = eventTime;
mTimeoutOverrideReleaseReason = RELEASE_REASON_UNKNOWN; // reset the reason
}
+
+ checkAndLogDimIfQualified(POLICY_REASON_OFF_POWER_BUTTON, eventTime);
+
} else if (changeReason == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
// Interactive Off reason is timeout
interactiveStateOffReason = OFF_REASON_TIMEOUT;
@@ -393,6 +526,8 @@
// state instantly
mTimeoutOffTimestamp = eventTime;
}
+
+ checkAndLogDimIfQualified(POLICY_REASON_OFF_TIMEOUT, eventTime);
}
long interactiveStateOnDurationMs =
@@ -462,6 +597,106 @@
}
}
+ private void checkAndLogDimIfQualified(
+ @PolicyReason int reasonToBeChecked, long eventTime) {
+ // Only log dim event when DEFAULT_DISPLAY
+ if (mPowerGroupId != DEFAULT_DISPLAY) {
+ return;
+ }
+
+ int dimDurationMs = 0;
+ int lastUserActivity = mCurrentUserActivityEvent;
+ int lastUserActivityDurationMs = (int) (eventTime - mCurrentUserActivityTimestamp);
+ switch (reasonToBeChecked) {
+ case POLICY_REASON_OFF_TIMEOUT: {
+ // The policy ordering:
+ // (1) --DIM--OFF/DOZE->| or (2) --DIM->| because OFF/DOZE hasn't been updated.
+ dimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp); //(1)--DIM->|
+ if (mPrevScreenPolicy == POLICY_DIM) { // for (2) --DIM--OFF/DOZE->|
+ dimDurationMs = mPrevScreenPolicyDurationMs;
+ }
+ mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+ mPhysicalDisplayPortIdForDefaultDisplay,
+ reasonToBeChecked,
+ lastUserActivity,
+ lastUserActivityDurationMs,
+ dimDurationMs,
+ mScreenOffTimeoutMs);
+ mPastDimDurationMs = dimDurationMs;
+ return;
+ }
+ case POLICY_REASON_OFF_POWER_BUTTON: {
+ // Power Off will be triggered by USER_ACTIVITY_EVENT_BUTTON
+ // The metric wants to record the previous activity before EVENT_BUTTON
+ lastUserActivity = mPrevUserActivityEvent;
+ lastUserActivityDurationMs = (int) (eventTime - mPrevUserActivityTimestamp);
+ // the policy ordering:
+ // (1) ---BRIGHT->| or (2) ---DIM->| because OFF/DOZE hasn't been updated
+ dimDurationMs = 0; // for (1) ---BRIGHT->| which doesn't have dim (no need log)
+ if (mCurrentScreenPolicy == POLICY_DIM) { // for (2) ---DIM->|
+ dimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+ mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+ mPhysicalDisplayPortIdForDefaultDisplay,
+ reasonToBeChecked,
+ lastUserActivity,
+ lastUserActivityDurationMs,
+ dimDurationMs,
+ mScreenOffTimeoutMs);
+ mHandler.removeCallbacksAndMessages(HANDLER_TOKEN);
+ }
+
+ mPastDimDurationMs = dimDurationMs;
+ return;
+ }
+ case POLICY_REASON_BRIGHT_UNDIM: {
+ // Has checked the latest screen policy is POLICY_BRIGHT in onScreenPolicyUpdate
+ if (mCurrentScreenPolicy == POLICY_DIM) { // policy ordering: --DIM--BRIGHT->|
+ int savedDimDurationMs = (int) (eventTime - mCurrentScreenPolicyTimestamp);
+ int savedLastUserActivity = lastUserActivity;
+ int savedLastUserActivityDurationMs = lastUserActivityDurationMs;
+
+ // For the undim case --DIM--BRIGHT->|, it needs wait 500 ms to
+ // differentiate between "power button off" case, which is
+ // --DIM--BRIGHT(<500ms)--OFF/DOZE->|
+ // [Method] Wait 500 ms to see whether triggers power button off or not.
+ // [Reason] We got --DIM--BRIGHT->|. However, if BRIGHT is so short (<500ms)
+ // and follows OFF/DOZE, it represents power button off, not undim.
+ // It is normal to have a short BRIGHT for power button off because
+ // the system need to play an animation before off.
+ mHandler.postDelayed(() -> {
+ mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+ mPhysicalDisplayPortIdForDefaultDisplay,
+ reasonToBeChecked,
+ savedLastUserActivity,
+ savedLastUserActivityDurationMs,
+ savedDimDurationMs,
+ mScreenOffTimeoutMs);
+ mPastDimDurationMs = savedDimDurationMs;
+ }, HANDLER_TOKEN, SCREEN_POLICY_DIM_POWER_OFF_BRIGHT_THRESHOLD_MILLIS);
+ }
+ return;
+ }
+ case POLICY_REASON_BRIGHT_INITIATED_REVERT: {
+ // the dimDuration in BRIGHT_INITIATE_REVERT is for the dim duration before
+ // screen interactive off (mPastDimDurationMs)
+ long offToOnDurationMs = eventTime - mInteractiveOffTimestamp;
+ if (mPastDimDurationMs > 0
+ && offToOnDurationMs < USER_INITIATED_REVERT_THRESHOLD_MILLIS) {
+ mWakefulnessSessionFrameworkStatsLogger.logDimEvent(
+ mPhysicalDisplayPortIdForDefaultDisplay,
+ reasonToBeChecked,
+ lastUserActivity,
+ lastUserActivityDurationMs,
+ mPastDimDurationMs,
+ mScreenOffTimeoutMs);
+ }
+ return;
+ }
+ default:
+ return;
+ }
+ }
+
void dump(IndentingPrintWriter writer) {
final long now = mClock.uptimeMillis();
@@ -475,6 +710,12 @@
final long prevUserActivityDurationMs = now - mPrevUserActivityTimestamp;
writer.println("previous user activity duration: " + prevUserActivityDurationMs);
writer.println("is in override timeout: " + isInOverrideTimeout());
+ writer.println("mIsInteractive: " + mIsInteractive);
+ writer.println("current screen policy: " + mCurrentScreenPolicy);
+ final long currentScreenPolicyDurationMs = now - mCurrentScreenPolicyTimestamp;
+ writer.println("current screen policy duration: " + currentScreenPolicyDurationMs);
+ writer.println("previous screen policy: " + mPrevScreenPolicy);
+ writer.println("past screen policy duration: " + mPrevScreenPolicyDurationMs);
writer.decreaseIndent();
}
}
@@ -512,6 +753,24 @@
(long) defaultTimeoutMs);
}
+ public void logDimEvent(
+ int physicalDisplayPortId,
+ @PolicyReason int policyReason,
+ @PowerManager.UserActivityEvent int userActivityEvent,
+ int lastUserActivityEventDurationMs,
+ int dimDurationMs,
+ int defaultTimeoutMs) {
+ int logUserActivityEvent = convertToLogUserActivityEvent(userActivityEvent);
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SCREEN_DIM_REPORTED,
+ physicalDisplayPortId,
+ policyReason,
+ logUserActivityEvent,
+ lastUserActivityEventDurationMs,
+ dimDurationMs,
+ defaultTimeoutMs);
+ }
+
private static final int USER_ACTIVITY_OTHER = FrameworkStatsLog
.SCREEN_INTERACTIVE_SESSION_REPORTED__LAST_USER_ACTIVITY_EVENT__OTHER;
@@ -591,5 +850,13 @@
Clock getClock() {
return SystemClock::uptimeMillis;
}
+
+ Handler getHandler() {
+ return BackgroundThread.getHandler();
+ }
+
+ DisplayManagerInternal getDisplayManagerInternal() {
+ return LocalServices.getService(DisplayManagerInternal.class);
+ }
}
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7e27407..f6c3d8e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -76,6 +76,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
@@ -109,12 +110,31 @@
@GuardedBy("mChannelMapLock")
private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
+ /*
+ * Multi-level map storing the session statistics since last pull from StatsD.
+ * The first level is keyed by the UID of the process owning the session.
+ * The second level is keyed by the tag of the session. The point of separating different
+ * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different
+ * behaviors.
+ */
+ @GuardedBy("mSessionSnapshotMapLock")
+ private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap;
+
/** Lock to protect mActiveSessions and the UidObserver. */
private final Object mLock = new Object();
/** Lock to protect mChannelMap. */
private final Object mChannelMapLock = new Object();
+ /*
+ * Lock to protect mSessionSnapshotMap.
+ * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided.
+ * We should grab these separately.
+ * When we need to have nested acquisitions, we should always follow the order of acquiring
+ * mSessionSnapshotMapLock first then mLock.
+ */
+ private final Object mSessionSnapshotMapLock = new Object();
+
@GuardedBy("mNonIsolatedTidsLock")
private final Map<Integer, Set<Long>> mNonIsolatedTids;
@@ -135,6 +155,8 @@
private int mPowerHalVersion;
private final PackageManager mPackageManager;
+ private boolean mUsesFmq;
+
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
@@ -162,6 +184,7 @@
}
mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
+ mSessionSnapshotMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
@@ -170,6 +193,7 @@
LocalServices.getService(ActivityManagerInternal.class));
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
+ mUsesFmq = false;
if (mPowerHal != null) {
try {
mPowerHalVersion = mPowerHal.getInterfaceVersion();
@@ -197,6 +221,134 @@
}
}
+ private class AppHintSessionSnapshot {
+ /*
+ * Per-Uid and Per-SessionTag snapshot that tracks metrics including
+ * number of created sessions, number of power efficienct sessions, and
+ * maximum number of threads in a session.
+ * Given that it's Per-SessionTag, each uid can have multiple snapshots.
+ */
+ int mCurrentSessionCount;
+ int mMaxConcurrentSession;
+ int mMaxThreadCount;
+ int mPowerEfficientSessionCount;
+
+ final int mTargetDurationNsCountPQSize = 100;
+ PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
+
+ class TargetDurationRecord implements Comparable<TargetDurationRecord> {
+ long mTargetDurationNs;
+ long mTimestamp;
+ int mCount;
+ TargetDurationRecord(long targetDurationNs) {
+ mTargetDurationNs = targetDurationNs;
+ mTimestamp = System.currentTimeMillis();
+ mCount = 1;
+ }
+
+ @Override
+ public int compareTo(TargetDurationRecord t) {
+ int tCount = t.getCount();
+ int thisCount = this.getCount();
+ // Here we sort in the order of number of count in ascending order.
+ // i.e. the lowest count of target duration is at the head of the queue.
+ // Upon same count, the tiebreaker is the timestamp, the older item will be at the
+ // front of the queue.
+ if (tCount == thisCount) {
+ return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1;
+ }
+ return (tCount < thisCount) ? 1 : -1;
+ }
+ long getTargetDurationNs() {
+ return mTargetDurationNs;
+ }
+
+ int getCount() {
+ return mCount;
+ }
+
+ long getTimestamp() {
+ return mTimestamp;
+ }
+
+ void setCount(int count) {
+ mCount = count;
+ }
+
+ void setTimestamp() {
+ mTimestamp = System.currentTimeMillis();
+ }
+
+ void setTargetDurationNs(long targetDurationNs) {
+ mTargetDurationNs = targetDurationNs;
+ }
+ }
+
+ AppHintSessionSnapshot() {
+ mCurrentSessionCount = 0;
+ mMaxConcurrentSession = 0;
+ mMaxThreadCount = 0;
+ mPowerEfficientSessionCount = 0;
+ mTargetDurationNsCountPQ = new PriorityQueue<>(1);
+ }
+
+ void updateUponSessionCreation(int threadCount, long targetDuration) {
+ mCurrentSessionCount += 1;
+ mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount);
+ mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+ updateTargetDurationNs(targetDuration);
+ }
+
+ void updateUponSessionClose() {
+ mCurrentSessionCount -= 1;
+ }
+
+ void logPowerEfficientSession() {
+ mPowerEfficientSessionCount += 1;
+ }
+
+ void updateThreadCount(int threadCount) {
+ mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+ }
+
+ void updateTargetDurationNs(long targetDurationNs) {
+ for (TargetDurationRecord t : mTargetDurationNsCountPQ) {
+ if (t.getTargetDurationNs() == targetDurationNs) {
+ t.setCount(t.getCount() + 1);
+ t.setTimestamp();
+ return;
+ }
+ }
+ if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) {
+ mTargetDurationNsCountPQ.poll();
+ }
+ mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs));
+ }
+
+ int getMaxConcurrentSession() {
+ return mMaxConcurrentSession;
+ }
+
+ int getMaxThreadCount() {
+ return mMaxThreadCount;
+ }
+
+ int getPowerEfficientSessionCount() {
+ return mPowerEfficientSessionCount;
+ }
+
+ long[] targetDurationNsList() {
+ final int listSize = 5;
+ long[] targetDurations = new long[listSize];
+ while (mTargetDurationNsCountPQ.size() > listSize) {
+ mTargetDurationNsCountPQ.poll();
+ }
+ for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) {
+ targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs();
+ }
+ return targetDurations;
+ }
+ }
private boolean isHalSupported() {
return mHintSessionPreferredRate != -1;
}
@@ -235,6 +387,11 @@
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR,
this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ this::onPullAtom);
}
private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
@@ -247,11 +404,82 @@
data.add(FrameworkStatsLog.buildStatsEvent(
FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
isSurfaceFlingerUsingCpuHint,
- isHwuiHintManagerEnabled));
+ isHwuiHintManagerEnabled,
+ getFmqUsage()));
+ }
+ if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) {
+ synchronized (mSessionSnapshotMapLock) {
+ for (int i = 0; i < mSessionSnapshotMap.size(); ++i) {
+ final int uid = mSessionSnapshotMap.keyAt(i);
+ final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.valueAt(i);
+ for (int j = 0; j < sessionSnapshots.size(); ++j) {
+ final int sessionTag = sessionSnapshots.keyAt(j);
+ final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j);
+ data.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+ uid,
+ sessionTag,
+ sessionSnapshot.getMaxConcurrentSession(),
+ sessionSnapshot.getMaxThreadCount(),
+ sessionSnapshot.getPowerEfficientSessionCount(),
+ sessionSnapshot.targetDurationNsList()
+ ));
+ }
+ }
+ }
+ restoreSessionSnapshot();
}
return android.app.StatsManager.PULL_SUCCESS;
}
+ private int getFmqUsage() {
+ if (mUsesFmq) {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED;
+ } else if (mPowerHalVersion < 5) {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET;
+ } else {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED;
+ }
+ }
+
+ private void restoreSessionSnapshot() {
+ // clean up snapshot map and rebuild with current active sessions
+ synchronized (mSessionSnapshotMapLock) {
+ mSessionSnapshotMap.clear();
+ synchronized (mLock) {
+ for (int i = 0; i < mActiveSessions.size(); i++) {
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.valueAt(i);
+ for (int j = 0; j < tokenMap.size(); j++) {
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+ for (int k = 0; k < sessionSet.size(); ++k) {
+ AppHintSession appHintSession = sessionSet.valueAt(k);
+ final int tag = appHintSession.getTag();
+ final int uid = appHintSession.getUid();
+ final long targetDuationNs =
+ appHintSession.getTargetDurationNs();
+ final int threadCount = appHintSession.getThreadIds().length;
+ ArrayMap<Integer, AppHintSessionSnapshot> snapshots =
+ mSessionSnapshotMap.get(uid);
+ if (snapshots == null) {
+ snapshots = new ArrayMap<>();
+ mSessionSnapshotMap.put(uid, snapshots);
+ }
+ AppHintSessionSnapshot snapshot = snapshots.get(tag);
+ if (snapshot == null) {
+ snapshot = new AppHintSessionSnapshot();
+ snapshots.put(tag, snapshot);
+ }
+ snapshot.updateUponSessionCreation(threadCount,
+ targetDuationNs);
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Wrapper around the static-native methods from native.
*
@@ -833,17 +1061,13 @@
// we change the session tag to SessionTag.GAME
// as it was not previously classified
switch (getUidApplicationCategory(callingUid)) {
- case ApplicationInfo.CATEGORY_GAME:
- tag = SessionTag.GAME;
- break;
- case ApplicationInfo.CATEGORY_UNDEFINED:
+ case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME;
+ case ApplicationInfo.CATEGORY_UNDEFINED ->
// We use CATEGORY_UNDEFINED to filter the case when
// PackageManager.NameNotFoundException is caught,
// which should not happen.
tag = SessionTag.APP;
- break;
- default:
- tag = SessionTag.APP;
+ default -> tag = SessionTag.APP;
}
}
@@ -889,9 +1113,15 @@
logPerformanceHintSessionAtom(
callingUid, sessionId, durationNanos, tids, tag);
+ synchronized (mSessionSnapshotMapLock) {
+ // Update session snapshot upon session creation
+ mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>())
+ .computeIfAbsent(tag, k -> new AppHintSessionSnapshot())
+ .updateUponSessionCreation(tids.length, durationNanos);
+ }
synchronized (mLock) {
- AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
- halSessionPtr, durationNanos);
+ AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids,
+ token, halSessionPtr, durationNanos);
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
mActiveSessions.get(callingUid);
if (tokenMap == null) {
@@ -904,6 +1134,8 @@
tokenMap.put(token, sessionSet);
}
sessionSet.add(hs);
+ mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
+
return hs;
}
} finally {
@@ -996,6 +1228,7 @@
final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
protected final int mUid;
protected final int mPid;
+ protected final int mTag;
protected int[] mThreadIds;
protected final IBinder mToken;
protected long mHalSessionPtr;
@@ -1003,6 +1236,7 @@
protected boolean mUpdateAllowedByProcState;
protected int[] mNewThreadIds;
protected boolean mPowerEfficient;
+ protected boolean mHasBeenPowerEfficient;
protected boolean mShouldForcePause;
private enum SessionModes {
@@ -1010,16 +1244,18 @@
};
protected AppHintSession(
- int uid, int pid, int[] threadIds, IBinder token,
+ int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
long halSessionPtr, long durationNanos) {
mUid = uid;
mPid = pid;
+ mTag = sessionTag;
mToken = token;
mThreadIds = threadIds;
mHalSessionPtr = halSessionPtr;
mTargetDurationNanos = durationNanos;
mUpdateAllowedByProcState = true;
mPowerEfficient = false;
+ mHasBeenPowerEfficient = false;
mShouldForcePause = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowedByProcState(allowed);
@@ -1056,6 +1292,20 @@
mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
mTargetDurationNanos = targetDurationNanos;
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos);
+ }
}
@Override
@@ -1108,6 +1358,20 @@
if (sessionSet.isEmpty()) tokenMap.remove(mToken);
if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.updateUponSessionClose();
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
final int[] tids = getTidsInternal();
@@ -1191,6 +1455,21 @@
mShouldForcePause = false;
}
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag "
+ + mTag);
+ return;
+ }
+ sessionSnapshot.updateThreadCount(tids.length);
+ }
}
public int[] getThreadIds() {
@@ -1231,6 +1510,26 @@
}
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
+ if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) {
+ if (!mHasBeenPowerEfficient) {
+ mHasBeenPowerEfficient = true;
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid
+ + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.logPowerEfficientSession();
+ }
+ }
+ }
}
@Override
@@ -1254,6 +1553,20 @@
}
}
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getTag() {
+ return mTag;
+ }
+
+ public long getTargetDurationNs() {
+ synchronized (this) {
+ return mTargetDurationNanos;
+ }
+ }
+
void validateWorkDuration(WorkDuration workDuration) {
if (DEBUG) {
Slogf.d(TAG, "WorkDuration("
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 6d519ee..90981ada 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -86,8 +86,7 @@
private ConsumedEnergyRetriever mConsumedEnergyRetriever;
private IntSupplier mVoltageSupplier;
private int[] mEnergyConsumerIds = new int[0];
- private WifiActivityEnergyInfo mLastWifiActivityInfo =
- new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+ private WifiActivityEnergyInfo mLastWifiActivityInfo;
private NetworkStats mLastNetworkStats;
private long[] mLastConsumedEnergyUws;
private int mLastVoltageMv;
@@ -206,14 +205,21 @@
return null;
}
- long rxDuration = activityInfo.getControllerRxDurationMillis()
- - mLastWifiActivityInfo.getControllerRxDurationMillis();
- long txDuration = activityInfo.getControllerTxDurationMillis()
- - mLastWifiActivityInfo.getControllerTxDurationMillis();
- long scanDuration = activityInfo.getControllerScanDurationMillis()
- - mLastWifiActivityInfo.getControllerScanDurationMillis();
- long idleDuration = activityInfo.getControllerIdleDurationMillis()
- - mLastWifiActivityInfo.getControllerIdleDurationMillis();
+ long rxDuration = 0;
+ long txDuration = 0;
+ long scanDuration = 0;
+ long idleDuration = 0;
+
+ if (mLastWifiActivityInfo != null) {
+ rxDuration = activityInfo.getControllerRxDurationMillis()
+ - mLastWifiActivityInfo.getControllerRxDurationMillis();
+ txDuration = activityInfo.getControllerTxDurationMillis()
+ - mLastWifiActivityInfo.getControllerTxDurationMillis();
+ scanDuration = activityInfo.getControllerScanDurationMillis()
+ - mLastWifiActivityInfo.getControllerScanDurationMillis();
+ idleDuration = activityInfo.getControllerIdleDurationMillis()
+ - mLastWifiActivityInfo.getControllerIdleDurationMillis();
+ }
mLayout.setDeviceRxTime(mDeviceStats, rxDuration);
mLayout.setDeviceTxTime(mDeviceStats, txDuration);
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6a5a7ac..d34498a 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -47,3 +47,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "add_battery_usage_stats_slice_atom"
+ namespace: "backstage_power"
+ description: "Adds battery_usage_stats_slice atom"
+ bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0041d39..bca81f52 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1283,7 +1283,7 @@
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
- new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true);
+ new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
new int[]{TRANSPORT_BLUETOOTH},
@@ -1542,6 +1542,11 @@
final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
+ // Set startTime before boot so that NetworkStats includes at least one full bucket.
+ // Set endTime in the future so that NetworkStats includes everything in the active bucket.
+ final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
+ final long endTime = currentTimeInMillis + bucketDuration;
+
// TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
// history when query in every second in order to show realtime statistics. However,
// this is not a good long-term solution since NetworkStatsService will make frequent
@@ -1552,9 +1557,7 @@
}
final android.app.usage.NetworkStats queryNonTaggedStats =
- getNetworkStatsManager().querySummary(
- template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
- currentTimeInMillis);
+ getNetworkStatsManager().querySummary(template, startTime, endTime);
final NetworkStats nonTaggedStats =
NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
@@ -1562,9 +1565,7 @@
if (!includeTags) return nonTaggedStats;
final android.app.usage.NetworkStats queryTaggedStats =
- getNetworkStatsManager().queryTaggedSummary(template,
- currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
- currentTimeInMillis);
+ getNetworkStatsManager().queryTaggedSummary(template, startTime, endTime);
final NetworkStats taggedStats =
NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats);
queryTaggedStats.close();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ddbd809..953aae9 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.trust;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
@@ -84,6 +85,9 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
@@ -159,6 +163,7 @@
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
+ private final LockSettingsInternal mLockSettings;
private final LockPatternUtils mLockPatternUtils;
private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
@@ -250,6 +255,20 @@
private final StrongAuthTracker mStrongAuthTracker;
+ // Used to subscribe to device credential auth attempts.
+ private final LockSettingsStateListener mLockSettingsStateListener =
+ new LockSettingsStateListener() {
+ @Override
+ public void onAuthenticationSucceeded(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 1, userId).sendToTarget();
+ }
+
+ @Override
+ public void onAuthenticationFailed(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 0, userId).sendToTarget();
+ }
+ };
+
private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM;
@@ -294,6 +313,7 @@
mHandler = createHandler(injector.getLooper());
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mLockSettings = LocalServices.getService(LockSettingsInternal.class);
mLockPatternUtils = injector.getLockPatternUtils();
mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
@@ -315,6 +335,9 @@
checkNewAgents();
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener);
+ }
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
diff --git a/services/core/java/com/android/server/updates/Android.bp b/services/core/java/com/android/server/updates/Android.bp
new file mode 100644
index 0000000..10beebb
--- /dev/null
+++ b/services/core/java/com/android/server/updates/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "updates_flags",
+ package: "com.android.server.updates",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "updates_flags_lib",
+ aconfig_declarations: "updates_flags",
+}
diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
index 5565b6f..af4025e 100644
--- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
@@ -16,17 +16,15 @@
package com.android.server.updates;
+import android.content.Context;
+import android.content.Intent;
import android.os.FileUtils;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.Base64;
import android.util.Slog;
-import com.android.internal.util.HexDump;
-
import libcore.io.Streams;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,10 +34,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver {
@@ -52,31 +47,31 @@
@Override
protected void install(InputStream inputStream, int version) throws IOException {
- /* Install is complicated here because we translate the input, which is a JSON file
- * containing log information to a directory with a file per log. To support atomically
- * replacing the old configuration directory with the new there's a bunch of steps. We
- * create a new directory with the logs and then do an atomic update of the current symlink
- * to point to the new directory.
- */
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ // To support atomically replacing the old configuration directory with the new there's a
+ // bunch of steps. We create a new directory with the logs and then do an atomic update of
+ // the current symlink to point to the new directory.
// 1. Ensure that the update dir exists and is readable
updateDir.mkdir();
if (!updateDir.isDirectory()) {
throw new IOException("Unable to make directory " + updateDir.getCanonicalPath());
}
if (!updateDir.setReadable(true, false)) {
- throw new IOException("Unable to set permissions on " +
- updateDir.getCanonicalPath());
+ throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath());
}
File currentSymlink = new File(updateDir, "current");
File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version));
- File oldDirectory;
// 2. Handle the corner case where the new directory already exists.
if (newVersion.exists()) {
// If the symlink has already been updated then the update died between steps 7 and 8
// and so we cannot delete the directory since its in use. Instead just bump the version
// and return.
if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) {
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
deleteOldLogDirectories();
return;
@@ -91,22 +86,12 @@
throw new IOException("Unable to make directory " + newVersion.getCanonicalPath());
}
if (!newVersion.setReadable(true, false)) {
- throw new IOException("Failed to set " +newVersion.getCanonicalPath() +
- " readable");
+ throw new IOException(
+ "Failed to set " + newVersion.getCanonicalPath() + " readable");
}
- // 4. For each log in the log file create the corresponding file in <new_version>/ .
- try {
- byte[] content = Streams.readFullyNoClose(inputStream);
- JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8));
- JSONArray logs = json.getJSONArray("logs");
- for (int i = 0; i < logs.length(); i++) {
- JSONObject log = logs.getJSONObject(i);
- installLog(newVersion, log);
- }
- } catch (JSONException e) {
- throw new IOException("Failed to parse logs", e);
- }
+ // 4. Validate the log list json and move the file in <new_version>/ .
+ installLogList(newVersion, inputStream);
// 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic
// update.
@@ -125,49 +110,38 @@
}
Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath());
// 7. Update the current version information
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
// 8. Cleanup
deleteOldLogDirectories();
}
- private void installLog(File directory, JSONObject logObject) throws IOException {
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ }
+
+ private void installLogList(File directory, InputStream inputStream) throws IOException {
try {
- String logFilename = getLogFileName(logObject.getString("key"));
- File file = new File(directory, logFilename);
- try (OutputStreamWriter out =
- new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
- writeLogEntry(out, "key", logObject.getString("key"));
- writeLogEntry(out, "url", logObject.getString("url"));
- writeLogEntry(out, "description", logObject.getString("description"));
+ byte[] content = Streams.readFullyNoClose(inputStream);
+ if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) {
+ throw new IOException("Log list data not valid");
+ }
+
+ File file = new File(directory, "log_list.json");
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(content);
}
if (!file.setReadable(true, false)) {
throw new IOException("Failed to set permissions on " + file.getCanonicalPath());
}
} catch (JSONException e) {
- throw new IOException("Failed to parse log", e);
+ throw new IOException("Malformed json in log list", e);
}
-
- }
-
- /**
- * Get the filename for a log based on its public key. This must be kept in sync with
- * org.conscrypt.ct.CTLogStoreImpl.
- */
- private String getLogFileName(String base64PublicKey) {
- byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT);
- try {
- byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes);
- return HexDump.toHexString(id, false);
- } catch (NoSuchAlgorithmException e) {
- // SHA-256 is guaranteed to be available.
- throw new RuntimeException(e);
- }
- }
-
- private void writeLogEntry(OutputStreamWriter out, String key, String value)
- throws IOException {
- out.write(key + ":" + value + "\n");
}
private void deleteOldLogDirectories() throws IOException {
@@ -175,12 +149,14 @@
return;
}
File currentTarget = new File(updateDir, "current").getCanonicalFile();
- FileFilter filter = new FileFilter() {
- @Override
- public boolean accept(File file) {
- return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX);
- }
- };
+ FileFilter filter =
+ new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !currentTarget.equals(file)
+ && file.getName().startsWith(LOGDIR_PREFIX);
+ }
+ };
for (File f : updateDir.listFiles(filter)) {
FileUtils.deleteContentsAndDir(f);
}
diff --git a/services/core/java/com/android/server/updates/flags.aconfig b/services/core/java/com/android/server/updates/flags.aconfig
new file mode 100644
index 0000000..476cb37
--- /dev/null
+++ b/services/core/java/com/android/server/updates/flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.updates"
+container: "system"
+
+flag {
+ name: "certificate_transparency_installer"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable certificate transparency installer for log list data"
+ bug: "319829948"
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 5be5bc5..2c73412 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -47,7 +47,7 @@
import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
+import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
import android.animation.ObjectAnimator;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 129cee7..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,7 +53,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -65,6 +64,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
@@ -89,12 +89,6 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -114,7 +108,6 @@
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
-import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -240,11 +233,10 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
+import static com.android.server.wm.DesktopModeLaunchParamsModifier.canEnterDesktopMode;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
@@ -343,7 +335,6 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -485,9 +476,6 @@
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
- // Rounding tolerance to be used in aspect ratio computations
- private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
-
final ActivityTaskManagerService mAtmService;
final ActivityCallerState mCallerState;
@NonNull
@@ -831,26 +819,6 @@
// naturally.
private boolean mInSizeCompatModeForBounds = false;
- // Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
- private boolean mIsAspectRatioApplied = false;
-
- // Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
- // for fixed orientation. If not null, they are used as parent container in
- // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets. If
- // letterboxed due to fixed orientation then aspect ratio restrictions are also respected.
- // This happens when an activity has fixed orientation which doesn't match orientation of the
- // parent because a display is ignoring orientation request or fixed to user rotation.
- // See WindowManagerService#getIgnoreOrientationRequest and
- // WindowManagerService#getFixedToUserRotation for more context.
- @Nullable
- private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
-
- // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
- // aspect ratio. If not null, they are used as parent container in
- // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
- @Nullable
- private Rect mLetterboxBoundsForAspectRatio;
-
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -892,8 +860,6 @@
/** The last set {@link DropInputMode} for this activity surface. */
@DropInputMode
private int mLastDropInputMode = DropInputMode.NONE;
- /** Whether the input to this activity will be dropped during the current playing animation. */
- private boolean mIsInputDroppedForAnimation;
/**
* Whether the application has desk mode resources. Calculated and cached when
@@ -1136,11 +1102,9 @@
pw.println(prefix + "mLastReportedConfigurations:");
mLastReportedConfiguration.dump(pw, prefix + " ");
- if (Flags.activityWindowInfoFlag()) {
- pw.print(prefix);
- pw.print("mLastReportedActivityWindowInfo=");
- pw.println(mLastReportedActivityWindowInfo);
- }
+ pw.print(prefix);
+ pw.print("mLastReportedActivityWindowInfo=");
+ pw.println(mLastReportedActivityWindowInfo);
pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
if (!getRequestedOverrideConfiguration().equals(EMPTY)) {
@@ -1733,15 +1697,6 @@
}
}
- /** Sets if all input will be dropped as a protection during the client-driven animation. */
- void setDropInputForAnimation(boolean isInputDroppedForAnimation) {
- if (mIsInputDroppedForAnimation == isInputDroppedForAnimation) {
- return;
- }
- mIsInputDroppedForAnimation = isInputDroppedForAnimation;
- updateUntrustedEmbeddingInputProtection();
- }
-
/**
* Sets to drop input when obscured to activity if it is embedded in untrusted mode.
*
@@ -1754,10 +1709,7 @@
if (getSurfaceControl() == null) {
return;
}
- if (mIsInputDroppedForAnimation) {
- // Disable all input during the animation.
- setDropInputMode(DropInputMode.ALL);
- } else if (isEmbeddedInUntrustedMode()) {
+ if (isEmbeddedInUntrustedMode()) {
// Set drop input to OBSCURED when untrusted embedded.
setDropInputMode(DropInputMode.OBSCURED);
} else {
@@ -3207,7 +3159,7 @@
@NonNull
ActivityWindowInfo getActivityWindowInfo() {
- if (!Flags.activityWindowInfoFlag() || !isAttached()) {
+ if (!isAttached()) {
return mTmpActivityWindowInfo;
}
if (isFixedRotationTransforming()) {
@@ -7221,7 +7173,7 @@
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
+ ", isAnimationSet=" + isAnimationSet);
if (!w.isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
@@ -8330,9 +8282,7 @@
}
void setLastReportedActivityWindowInfo(@NonNull ActivityWindowInfo activityWindowInfo) {
- if (Flags.activityWindowInfoFlag()) {
- mLastReportedActivityWindowInfo.set(activityWindowInfo);
- }
+ mLastReportedActivityWindowInfo.set(activityWindowInfo);
}
@Nullable
@@ -8392,7 +8342,7 @@
* aspect ratio.
*/
boolean shouldCreateCompatDisplayInsets() {
- if (mLetterboxUiController.hasFullscreenOverride()) {
+ if (mAppCompatController.getAppCompatAspectRatioOverrides().hasFullscreenOverride()) {
// If the user has forced the applications aspect ratio to be fullscreen, don't use size
// compatibility mode in any situation. The user has been warned and therefore accepts
// the risk of the application misbehaving.
@@ -8481,10 +8431,9 @@
fullConfig.windowConfiguration.getRotation());
}
- final Rect letterboxedContainerBounds =
- mLetterboxBoundsForFixedOrientationAndAspectRatio != null
- ? mLetterboxBoundsForFixedOrientationAndAspectRatio
- : mLetterboxBoundsForAspectRatio;
+ final Rect letterboxedContainerBounds = mAppCompatController
+ .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
new CompatDisplayInsets(
@@ -8558,10 +8507,8 @@
newParentConfiguration = mTmpConfig;
}
- mIsAspectRatioApplied = false;
+ mAppCompatController.getAppCompatAspectRatioPolicy().reset();
mIsEligibleForFixedOrientationLetterbox = false;
- mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
- mLetterboxBoundsForAspectRatio = null;
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
isFixedRotationTransforming());
@@ -8594,8 +8541,11 @@
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
- && !mLetterboxUiController.hasFullscreenOverride()) {
+ if (Flags.immersiveAppRepositioning()
+ && !mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()
+ && !mAppCompatController.getAppCompatAspectRatioOverrides()
+ .hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
@@ -8615,8 +8565,12 @@
// are already calculated in resolveFixedOrientationConfiguration, or if in size compat
// mode, it should already be calculated in resolveSizeCompatModeConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (!Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio()
- && !mInSizeCompatModeForBounds && !mLetterboxUiController.hasFullscreenOverride()) {
+ if (!Flags.immersiveAppRepositioning()
+ && !mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()
+ && !mInSizeCompatModeForBounds
+ && !mAppCompatController.getAppCompatAspectRatioOverrides()
+ .hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8633,14 +8587,16 @@
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
// ignoreOrientationRequest disabled.
- && (mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ && (mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()
// Limiting check for aspect ratio letterboxing to devices with enabled
// ignoreOrientationRequest. This avoids affecting phones where apps may
// not expect the change of smallestScreenWidthDp after rotation which is
// possible with this logic. Not having smallestScreenWidthDp completely
// accurate on phones shouldn't make the big difference and is expected
// to be already well-tested by apps.
- || (isIgnoreOrientationRequest && mIsAspectRatioApplied))) {
+ || (isIgnoreOrientationRequest
+ && mAppCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied()))) {
// TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
// rotations and only re-calculate if parent bounds have non-orientation size change.
resolvedConfig.smallestScreenWidthDp =
@@ -8681,7 +8637,14 @@
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
- applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+ applySizeOverrideIfNeeded(
+ mDisplayContent,
+ info.applicationInfo,
+ newParentConfiguration,
+ resolvedConfig,
+ mOptOutEdgeToEdge,
+ hasFixedRotationTransform(),
+ getCompatDisplayInsets() != null);
mResolveConfigHint.resetTmpOverrides();
logAppCompatState();
@@ -8691,100 +8654,6 @@
return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
}
- /**
- * If necessary, override configuration fields related to app bounds.
- * This will happen when the app is targeting SDK earlier than 35.
- * The insets and configuration has decoupled since SDK level 35, to make the system
- * compatible to existing apps, override the configuration with legacy metrics. In legacy
- * metrics, fields such as appBounds will exclude some of the system bar areas.
- * The override contains all potentially affected fields in Configuration, including
- * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
- * All overrides to those fields should be in this method.
- *
- * TODO: Consider integrate this with computeConfigByResolveHint()
- */
- private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
- int parentWindowingMode, Configuration inOutConfig) {
- if (mDisplayContent == null) {
- return;
- }
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
- rotation = mDisplayContent.getRotation();
- }
- if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
- || getCompatDisplayInsets() != null
- || (isFloating(parentWindowingMode)
- // Check the requested windowing mode of activity as well in case it is
- // switching between PiP and fullscreen.
- && (inOutConfig.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_UNDEFINED
- || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
- || rotation == ROTATION_UNDEFINED)) {
- // If the insets configuration decoupled logic is not enabled for the app, or the app
- // already has a compat override, or the context doesn't contain enough info to
- // calculate the override, skip the override.
- return;
- }
- // Make sure the orientation related fields will be updated by the override insets, because
- // fixed rotation has assigned the fields from display's configuration.
- if (hasFixedRotationTransform()) {
- inOutConfig.windowConfiguration.setAppBounds(null);
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.orientation = ORIENTATION_UNDEFINED;
- }
-
- // Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
- : mDisplayContent.mBaseDisplayWidth;
- final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
- : mDisplayContent.mBaseDisplayHeight;
- final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(nonDecorInsets);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- }
- if (inOutConfig.smallestScreenWidthDp
- == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
- mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
- mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
- }
-
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@NonNull Configuration parentConfig) {
task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
@@ -8844,11 +8713,13 @@
// letterboxed for fixed orientation. Aspect ratio restrictions are also applied if
// present. But this doesn't return true when the activity is letterboxed only because
// of aspect ratio restrictions.
- if (isLetterboxedForFixedOrientationAndAspectRatio()) {
+ if (mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
}
// Letterbox for limited aspect ratio.
- if (isLetterboxedForAspectRatioOnly()) {
+ if (mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForAspectRatioOnly()) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
}
@@ -8999,26 +8870,6 @@
}
/**
- * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
- * orientation then aspect ratio restrictions are also already respected.
- *
- * <p>This happens when an activity has fixed orientation which doesn't match orientation of the
- * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link
- * WindowManagerService#getIgnoreOrientationRequest} for more context.
- */
- boolean isLetterboxedForFixedOrientationAndAspectRatio() {
- return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
- }
-
- boolean isLetterboxedForAspectRatioOnly() {
- return mLetterboxBoundsForAspectRatio != null;
- }
-
- boolean isAspectRatioApplied() {
- return mIsAspectRatioApplied;
- }
-
- /**
* Whether this activity is eligible for letterbox eduction.
*
* <p>Conditions that need to be met:
@@ -9050,7 +8901,7 @@
* @param parentBounds are the new parent bounds passed down to the activity and should be used
* to compute the stable bounds.
* @param outStableBounds will store the stable bounds, which are the bounds with insets
- * applied, if orientation is not respected when insets are applied.
+ * applied, if orientation is not respected when insets are applied.g
* Stable bounds should be used to compute letterboxed bounds if
* orientation is not respected when insets are applied.
* @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
@@ -9203,23 +9054,9 @@
final Rect prevResolvedBounds = new Rect(resolvedBounds);
resolvedBounds.set(containingBounds);
- final float letterboxAspectRatioOverride =
- mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
-
- // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
- // be respected in #applyAspectRatio.
- final float desiredAspectRatio;
- if (isDefaultMultiWindowLetterboxAspectRatioDesired(newParentConfig)) {
- desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
- } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
- desiredAspectRatio = letterboxAspectRatioOverride;
- } else {
- desiredAspectRatio = computeAspectRatio(parentBounds);
- }
-
- // Apply aspect ratio to resolved bounds
- mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
- containingBounds, desiredAspectRatio);
+ mAppCompatController.getAppCompatAspectRatioPolicy()
+ .applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
+ containingBoundsWithInsets, containingBounds);
if (compatDisplayInsets != null) {
compatDisplayInsets.getBoundsByRotation(mTmpBounds,
@@ -9247,21 +9084,8 @@
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
- mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
- }
-
- /**
- * Returns {@code true} if the default aspect ratio for a letterboxed app in multi-window mode
- * should be used.
- */
- private boolean isDefaultMultiWindowLetterboxAspectRatioDesired(
- @NonNull Configuration parentConfig) {
- if (mDisplayContent == null) {
- return false;
- }
- final int windowingMode = parentConfig.windowConfiguration.getWindowingMode();
- return WindowConfiguration.inMultiWindowMode(windowingMode)
- && !mDisplayContent.getIgnoreOrientationRequest();
+ mAppCompatController.getAppCompatAspectRatioPolicy()
+ .setLetterboxBoundsForFixedOrientationAndAspectRatio(new Rect(resolvedBounds));
}
/**
@@ -9278,7 +9102,8 @@
// Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
// restricted size (resolved bounds may be the requested override bounds).
mTmpBounds.setEmpty();
- mIsAspectRatioApplied = applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
+ mAppCompatController.getAppCompatAspectRatioPolicy()
+ .applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
// If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
// then they should be aligned later in #updateResolvedBoundsPosition()
if (!mTmpBounds.isEmpty()) {
@@ -9289,7 +9114,8 @@
// restrict, the bounds should be the requested override bounds.
mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
- mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
+ mAppCompatController.getAppCompatAspectRatioPolicy()
+ .setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
}
}
@@ -9307,7 +9133,10 @@
// activity will be displayed within them even if it is in size compat mode. They should be
// saved here before resolved bounds are overridden below.
final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
- ? isAspectRatioApplied() : isLetterboxedForFixedOrientationAndAspectRatio();
+ ? mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isAspectRatioApplied()
+ : mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio();
final Rect containerBounds = useResolvedBounds
? new Rect(resolvedBounds)
: newParentConfiguration.windowConfiguration.getBounds();
@@ -9351,8 +9180,9 @@
resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
if (!compatDisplayInsets.mIsFloating) {
- mIsAspectRatioApplied =
- applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
+ mAppCompatController.getAppCompatAspectRatioPolicy()
+ .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+ containingBounds);
}
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
@@ -9437,18 +9267,24 @@
}
void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
- // Only allow to scale down.
mSizeCompatScale = mAppCompatController.getTransparentPolicy()
.findOpaqueNotFinishingActivityBelow()
.map(activityRecord -> activityRecord.mSizeCompatScale)
- .orElseGet(() -> {
- final int contentW = resolvedAppBounds.width();
- final int contentH = resolvedAppBounds.height();
- final int viewportW = containerAppBounds.width();
- final int viewportH = containerAppBounds.height();
- return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min(
- (float) viewportW / contentW, (float) viewportH / contentH);
- });
+ .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+ }
+
+ private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
+ final int contentW = resolvedAppBounds.width();
+ final int contentH = resolvedAppBounds.height();
+ final int viewportW = containerAppBounds.width();
+ final int viewportH = containerAppBounds.height();
+ // Allow an application to be up-scaled if its window is smaller than its
+ // original container or if it's a freeform window in desktop mode.
+ boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+ || (canEnterDesktopMode(mAtmService.mContext)
+ && getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ return shouldAllowUpscaling ? Math.min(
+ (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
}
private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
@@ -9756,190 +9592,15 @@
return mPauseConfigurationDispatchCount > 0;
}
- private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
- Rect containingBounds) {
- return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
- 0 /* desiredAspectRatio */);
- }
-
- /**
- * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
- * made to outBounds.
- *
- * @return {@code true} if aspect ratio restrictions were applied.
- */
- // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
- Rect containingBounds, float desiredAspectRatio) {
- final float maxAspectRatio = getMaxAspectRatio();
- final Task rootTask = getRootTask();
- final float minAspectRatio = getMinAspectRatio();
- final TaskFragment organizedTf = getOrganizedTaskFragment();
- float aspectRatioToApply = desiredAspectRatio;
- if (task == null || rootTask == null
- || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
- // Don't set aspect ratio if we are in VR mode.
- || isInVrUiMode(getConfiguration())
- // TODO(b/232898850): Always respect aspect ratio requests.
- // Don't set aspect ratio for activity in ActivityEmbedding split.
- || (organizedTf != null && !organizedTf.fillsParent())) {
- return false;
- }
-
- final int containingAppWidth = containingAppBounds.width();
- final int containingAppHeight = containingAppBounds.height();
- final float containingRatio = computeAspectRatio(containingAppBounds);
-
- if (aspectRatioToApply < 1) {
- aspectRatioToApply = containingRatio;
- }
-
- if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
- aspectRatioToApply = maxAspectRatio;
- } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
- aspectRatioToApply = minAspectRatio;
- }
-
- int activityWidth = containingAppWidth;
- int activityHeight = containingAppHeight;
-
- if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
- if (containingAppWidth < containingAppHeight) {
- // Width is the shorter side, so we use that to figure-out what the max. height
- // should be given the aspect ratio.
- activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
- } else {
- // Height is the shorter side, so we use that to figure-out what the max. width
- // should be given the aspect ratio.
- activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
- }
- } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
- boolean adjustWidth;
- switch (getRequestedConfigurationOrientation()) {
- case ORIENTATION_LANDSCAPE:
- // Width should be the longer side for this landscape app, so we use the width
- // to figure-out what the max. height should be given the aspect ratio.
- adjustWidth = false;
- break;
- case ORIENTATION_PORTRAIT:
- // Height should be the longer side for this portrait app, so we use the height
- // to figure-out what the max. width should be given the aspect ratio.
- adjustWidth = true;
- break;
- default:
- // This app doesn't have a preferred orientation, so we keep the length of the
- // longer side, and use it to figure-out the length of the shorter side.
- if (containingAppWidth < containingAppHeight) {
- // Width is the shorter side, so we use the height to figure-out what the
- // max. width should be given the aspect ratio.
- adjustWidth = true;
- } else {
- // Height is the shorter side, so we use the width to figure-out what the
- // max. height should be given the aspect ratio.
- adjustWidth = false;
- }
- break;
- }
- if (adjustWidth) {
- activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
- } else {
- activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
- }
- }
-
- if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
- // The display matches or is less than the activity aspect ratio, so nothing else to do.
- return false;
- }
-
- // Compute configuration based on max or min supported width and height.
- // Also account for the insets (e.g. display cutouts, navigation bar), which will be
- // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
- // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
- // the app bounds would end up too small. To achieve this we will also add clippable insets
- // when the corresponding dimension fully fills the parent
-
- int right = activityWidth + containingAppBounds.left;
- int left = containingAppBounds.left;
- if (right >= containingAppBounds.right) {
- right = containingBounds.right;
- left = containingBounds.left;
- }
- int bottom = activityHeight + containingAppBounds.top;
- int top = containingAppBounds.top;
- if (bottom >= containingAppBounds.bottom) {
- bottom = containingBounds.bottom;
- top = containingBounds.top;
- }
- outBounds.set(left, top, right, bottom);
- return true;
- }
-
/**
* Returns the min aspect ratio of this activity.
*/
float getMinAspectRatio() {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- return mAppCompatController.getTransparentPolicy().getInheritedMinAspectRatio();
- }
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
- if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
- return mLetterboxUiController.getUserMinAspectRatio();
- }
- if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
- && !mAppCompatController.getAppCompatCameraOverrides()
- .shouldOverrideMinAspectRatioForCamera()) {
- return info.getMinAspectRatio();
- }
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
- && !ActivityInfo.isFixedOrientationPortrait(
- getOverrideOrientation())) {
- return info.getMinAspectRatio();
- }
-
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
- && isParentFullscreenPortrait()) {
- // We are using the parent configuration here as this is the most recent one that gets
- // passed to onConfigurationChanged when a relevant change takes place
- return info.getMinAspectRatio();
- }
-
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
- return Math.max(mLetterboxUiController.getSplitScreenAspectRatio(),
- info.getMinAspectRatio());
- }
-
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
- return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
- info.getMinAspectRatio());
- }
-
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
- return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
- info.getMinAspectRatio());
- }
-
- if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_SMALL)) {
- return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
- info.getMinAspectRatio());
- }
- return info.getMinAspectRatio();
- }
-
- private boolean isParentFullscreenPortrait() {
- final WindowContainer parent = getParent();
- return parent != null
- && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
- && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ return mAppCompatController.getAppCompatAspectRatioPolicy().getMinAspectRatio();
}
float getMaxAspectRatio() {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- return mAppCompatController.getTransparentPolicy().getInheritedMaxAspectRatio();
- }
- return info.getMaxAspectRatio();
+ return mAppCompatController.getAppCompatAspectRatioPolicy().getMaxAspectRatio();
}
/**
@@ -9950,18 +9611,6 @@
}
/**
- * Returns the aspect ratio of the given {@code rect}.
- */
- static float computeAspectRatio(Rect rect) {
- final int width = rect.width();
- final int height = rect.height();
- if (width == 0 || height == 0) {
- return 0;
- }
- return Math.max(width, height) / (float) Math.min(width, height);
- }
-
- /**
* @return {@code true} if this activity was reparented to another display but
* {@link #ensureActivityConfiguration} is not called.
*/
@@ -10049,8 +9698,8 @@
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo();
- final boolean isActivityWindowInfoChanged = Flags.activityWindowInfoFlag()
- && !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo);
+ final boolean isActivityWindowInfoChanged =
+ !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo);
if (!displayChanged && !isActivityWindowInfoChanged
&& getConfiguration().equals(mTmpConfig)) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
@@ -10187,6 +9836,15 @@
*/
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
int configChanged = info.getRealConfigChanged();
+ if (android.content.res.Flags.handleAllConfigChanges()) {
+ if ((configChanged & CONFIG_RESOURCES_UNUSED) != 0) {
+ // Don't relaunch any activities that claim they do not use resources at all.
+ // If they still do, the onConfigurationChanged() callback will get called to
+ // let them know anyway.
+ return false;
+ }
+ }
+
boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);
// Override for apps targeting pre-O sdks
@@ -10215,8 +9873,8 @@
*/
private boolean onlyVrUiModeChanged(int changes, Configuration lastReportedConfig) {
final Configuration currentConfig = getConfiguration();
- return changes == CONFIG_UI_MODE && (isInVrUiMode(currentConfig)
- != isInVrUiMode(lastReportedConfig));
+ return changes == CONFIG_UI_MODE && (AppCompatUtils.isInVrUiMode(currentConfig)
+ != AppCompatUtils.isInVrUiMode(lastReportedConfig));
}
/**
@@ -10588,10 +10246,6 @@
return r;
}
- private static boolean isInVrUiMode(Configuration config) {
- return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
- }
-
private static boolean isInDeskUiMode(Configuration config) {
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
}
@@ -10815,16 +10469,19 @@
mAppCompatController.getAppCompatCameraOverrides()
.shouldRefreshActivityViaPauseForCameraCompat());
proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
- mLetterboxUiController.shouldOverrideMinAspectRatio());
+ mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
mLetterboxUiController.shouldOverrideForceResizeApp());
proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
- mLetterboxUiController.shouldEnableUserAspectRatioSettings());
+ mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
- mLetterboxUiController.isUserFullscreenOverrideEnabled());
+ mAppCompatController.getAppCompatAspectRatioOverrides()
+ .isUserFullscreenOverrideEnabled());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5b17875..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+ values.orientation);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
@@ -5183,6 +5187,10 @@
String hostingType) {
if (!mStartingProcessActivities.contains(activity)) {
mStartingProcessActivities.add(activity);
+ // Let the activity with higher z-order be started first.
+ if (mStartingProcessActivities.size() > 1) {
+ mStartingProcessActivities.sort(null /* by WindowContainer#compareTo */);
+ }
} else if (mProcessNames.get(
activity.processName, activity.info.applicationInfo.uid) != null) {
// The process is already starting. Wait for it to attach.
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
new file mode 100644
index 0000000..cf008e7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.utils.OptPropFactory;
+
+import java.util.function.Function;
+
+/**
+ * Encapsulates app compat configurations and overrides related to aspect ratio.
+ */
+class AppCompatAspectRatioOverrides {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "AppCompatAspectRatioOverrides" : TAG_ATM;
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final LetterboxConfiguration mLetterboxConfiguration;
+ @NonNull
+ private final UserAspectRatioState mUserAspectRatioState;
+
+ @NonNull
+ private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
+ @NonNull
+ private final Function<Boolean, Boolean> mIsDisplayFullScreenAndInPostureProvider;
+ @NonNull
+ private final Function<Configuration, Float> mGetHorizontalPositionMultiplierProvider;
+
+ AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull LetterboxConfiguration letterboxConfiguration,
+ @NonNull OptPropFactory optPropBuilder,
+ @NonNull Function<Boolean, Boolean> isDisplayFullScreenAndInPostureProvider,
+ @NonNull Function<Configuration, Float> getHorizontalPositionMultiplierProvider) {
+ mActivityRecord = activityRecord;
+ mLetterboxConfiguration = letterboxConfiguration;
+ mUserAspectRatioState = new UserAspectRatioState();
+ mIsDisplayFullScreenAndInPostureProvider = isDisplayFullScreenAndInPostureProvider;
+ mGetHorizontalPositionMultiplierProvider = getHorizontalPositionMultiplierProvider;
+ mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE,
+ mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled);
+ mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled);
+ mAllowOrientationOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ }
+
+ /**
+ * Whether we should apply the min aspect ratio per-app override. When this override is applied
+ * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
+ * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
+ * apply if no value is provided in the manifest.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideMinAspectRatio() {
+ return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+ isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
+ }
+
+ /**
+ * Whether we should apply the user aspect ratio override to the min aspect ratio for the
+ * current app.
+ */
+ boolean shouldApplyUserMinAspectRatioOverride() {
+ if (!shouldEnableUserAspectRatioSettings()) {
+ return false;
+ }
+
+ mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+ return mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+ && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
+ && mUserAspectRatioState.mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ }
+
+ boolean shouldApplyUserFullscreenOverride() {
+ if (isUserFullscreenOverrideEnabled()) {
+ mUserAspectRatioState.mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+ return mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ }
+
+ return false;
+ }
+
+ boolean isUserFullscreenOverrideEnabled() {
+ if (mAllowUserAspectRatioOverrideOptProp.isFalse()
+ || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
+ || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean isSystemOverrideToFullscreenEnabled() {
+ return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
+ && !mAllowOrientationOverrideOptProp.isFalse()
+ && (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ || mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ }
+
+ /**
+ * Whether we should enable users to resize the current app.
+ */
+ boolean shouldEnableUserAspectRatioSettings() {
+ // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
+ // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
+ // the current app doesn't opt-out so the first part of the predicate is true.
+ return !mAllowUserAspectRatioOverrideOptProp.isFalse()
+ && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+ && mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
+ }
+
+ boolean hasFullscreenOverride() {
+ // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
+ return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
+ }
+
+ float getUserMinAspectRatio() {
+ switch (mUserAspectRatioState.mUserAspectRatio) {
+ case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ return getDisplaySizeMinAspectRatio();
+ case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ return getSplitScreenAspectRatio();
+ case USER_MIN_ASPECT_RATIO_16_9:
+ return 16 / 9f;
+ case USER_MIN_ASPECT_RATIO_4_3:
+ return 4 / 3f;
+ case USER_MIN_ASPECT_RATIO_3_2:
+ return 3 / 2f;
+ default:
+ throw new AssertionError("Unexpected user min aspect ratio override: "
+ + mUserAspectRatioState.mUserAspectRatio);
+ }
+ }
+
+ float getSplitScreenAspectRatio() {
+ // Getting the same aspect ratio that apps get in split screen.
+ final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+ if (displayArea == null) {
+ return getDefaultMinAspectRatioForUnresizableApps();
+ }
+ int dividerWindowWidth =
+ getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
+ int dividerInsets =
+ getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
+ int dividerSize = dividerWindowWidth - dividerInsets * 2;
+ final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+ if (bounds.width() >= bounds.height()) {
+ bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+ bounds.right = bounds.centerX();
+ } else {
+ bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+ bounds.bottom = bounds.centerY();
+ }
+ return AppCompatUtils.computeAspectRatio(bounds);
+ }
+
+ float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
+ return shouldUseSplitScreenAspectRatio(parentConfiguration)
+ ? getSplitScreenAspectRatio()
+ : mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : getDefaultMinAspectRatio();
+ }
+
+ private float getDisplaySizeMinAspectRatio() {
+ final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+ if (displayArea == null) {
+ return mActivityRecord.info.getMinAspectRatio();
+ }
+ final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+ return AppCompatUtils.computeAspectRatio(bounds);
+ }
+
+ private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
+ final boolean isBookMode = mIsDisplayFullScreenAndInPostureProvider
+ .apply(/* isTabletop */false);
+ final boolean isNotCenteredHorizontally = mGetHorizontalPositionMultiplierProvider.apply(
+ parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
+ final boolean isTabletopMode = mIsDisplayFullScreenAndInPostureProvider
+ .apply(/* isTabletop */ true);
+ final boolean isLandscape = isFixedOrientationLandscape(
+ mActivityRecord.getOverrideOrientation());
+ final AppCompatCameraOverrides cameraOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+ final AppCompatCameraPolicy cameraPolicy =
+ mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
+ // Don't resize to split screen size when in book mode if letterbox position is centered
+ return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
+ || cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
+ && (cameraPolicy != null
+ && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
+ }
+
+ @VisibleForTesting
+ int getUserMinAspectRatioOverrideCode() {
+ try {
+ return mActivityRecord.mAtmService.getPackageManager()
+ .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+ }
+ return mUserAspectRatioState.mUserAspectRatio;
+ }
+
+ private float getDefaultMinAspectRatioForUnresizableApps() {
+ if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
+ || mActivityRecord.getDisplayArea() == null) {
+ return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+ ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ : getDefaultMinAspectRatio();
+ }
+
+ return getSplitScreenAspectRatio();
+ }
+
+ private float getDefaultMinAspectRatio() {
+ if (mActivityRecord.getDisplayArea() == null
+ || !mLetterboxConfiguration
+ .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
+ return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ }
+ return getDisplaySizeMinAspectRatio();
+ }
+
+ private static class UserAspectRatioState {
+ // TODO(b/315140179): Make mUserAspectRatio final
+ // The min aspect ratio override set by user
+ @PackageManager.UserMinAspectRatio
+ private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+ }
+
+ private boolean isCompatChangeEnabled(long overrideChangeId) {
+ return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
+ private Resources getResources() {
+ return mActivityRecord.mWmService.mContext.getResources();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
new file mode 100644
index 0000000..a7d2ecc
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+/**
+ * Encapsulate app compat policy logic related to aspect ratio.
+ */
+class AppCompatAspectRatioPolicy {
+
+ // Rounding tolerance to be used in aspect ratio computations
+ private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final TransparentPolicy mTransparentPolicy;
+ @NonNull
+ private final AppCompatOverrides mAppCompatOverrides;
+ @NonNull
+ private final AppCompatAspectRatioState mAppCompatAspectRatioState;
+
+ AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull TransparentPolicy transparentPolicy,
+ @NonNull AppCompatOverrides appCompatOverrides) {
+ mActivityRecord = activityRecord;
+ mTransparentPolicy = transparentPolicy;
+ mAppCompatOverrides = appCompatOverrides;
+ mAppCompatAspectRatioState = new AppCompatAspectRatioState();
+ }
+
+ /**
+ * Starts the evaluation of app compat aspect ratio when a new configuration needs to be
+ * resolved.
+ */
+ void reset() {
+ mAppCompatAspectRatioState.reset();
+ }
+
+ float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
+ @NonNull Rect parentBounds) {
+ final float letterboxAspectRatioOverride =
+ mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+ .getFixedOrientationLetterboxAspectRatio(newParentConfig);
+ // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
+ // be respected in #applyAspectRatio.
+ if (isDefaultMultiWindowLetterboxAspectRatioDesired(newParentConfig)) {
+ return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+ } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+ return letterboxAspectRatioOverride;
+ }
+ return AppCompatUtils.computeAspectRatio(parentBounds);
+ }
+
+ void applyDesiredAspectRatio(@NonNull Configuration newParentConfig, @NonNull Rect parentBounds,
+ @NonNull Rect resolvedBounds, @NonNull Rect containingBoundsWithInsets,
+ @NonNull Rect containingBounds) {
+ final float desiredAspectRatio = getDesiredAspectRatio(newParentConfig, parentBounds);
+ mAppCompatAspectRatioState.mIsAspectRatioApplied = applyAspectRatio(resolvedBounds,
+ containingBoundsWithInsets, containingBounds, desiredAspectRatio);
+ }
+
+ void applyAspectRatioForLetterbox(Rect outBounds, Rect containingAppBounds,
+ Rect containingBounds) {
+ mAppCompatAspectRatioState.mIsAspectRatioApplied = applyAspectRatio(outBounds,
+ containingAppBounds, containingBounds, 0 /* desiredAspectRatio */);
+ }
+
+ /**
+ * @return {@code true} when an app compat aspect ratio has been applied.
+ */
+ boolean isAspectRatioApplied() {
+ return mAppCompatAspectRatioState.mIsAspectRatioApplied;
+ }
+
+ /**
+ * Returns the min aspect ratio of this activity.
+ */
+ float getMinAspectRatio() {
+ if (mTransparentPolicy.isRunning()) {
+ return mTransparentPolicy.getInheritedMinAspectRatio();
+ }
+ final ActivityInfo info = mActivityRecord.info;
+ if (info.applicationInfo == null) {
+ return info.getMinAspectRatio();
+ }
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
+ return aspectRatioOverrides.getUserMinAspectRatio();
+ }
+ if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
+ && !mAppCompatOverrides.getAppCompatCameraOverrides()
+ .shouldOverrideMinAspectRatioForCamera()) {
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+ && !ActivityInfo.isFixedOrientationPortrait(
+ mActivityRecord.getOverrideOrientation())) {
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
+ && isParentFullscreenPortrait()) {
+ // We are using the parent configuration here as this is the most recent one that gets
+ // passed to onConfigurationChanged when a relevant change takes place
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+ return Math.max(aspectRatioOverrides.getSplitScreenAspectRatio(),
+ info.getMinAspectRatio());
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+ return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ info.getMinAspectRatio());
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+ return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ info.getMinAspectRatio());
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_SMALL)) {
+ return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
+ info.getMinAspectRatio());
+ }
+ return info.getMinAspectRatio();
+ }
+
+ float getMaxAspectRatio() {
+ if (mTransparentPolicy.isRunning()) {
+ return mTransparentPolicy.getInheritedMaxAspectRatio();
+ }
+ return mActivityRecord.info.getMaxAspectRatio();
+ }
+
+ @Nullable
+ Rect getLetterboxedContainerBounds() {
+ return mAppCompatAspectRatioState.getLetterboxedContainerBounds();
+ }
+
+ /**
+ * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
+ * orientation then aspect ratio restrictions are also already respected.
+ *
+ * <p>This happens when an activity has fixed orientation which doesn't match orientation of the
+ * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link
+ * WindowManagerService#getIgnoreOrientationRequest} for more context.
+ */
+ boolean isLetterboxedForFixedOrientationAndAspectRatio() {
+ return mAppCompatAspectRatioState.isLetterboxedForFixedOrientationAndAspectRatio();
+ }
+
+ boolean isLetterboxedForAspectRatioOnly() {
+ return mAppCompatAspectRatioState.isLetterboxedForAspectRatioOnly();
+ }
+
+ void setLetterboxBoundsForFixedOrientationAndAspectRatio(@NonNull Rect bounds) {
+ mAppCompatAspectRatioState.mLetterboxBoundsForFixedOrientationAndAspectRatio = bounds;
+ }
+
+ void setLetterboxBoundsForAspectRatio(@NonNull Rect bounds) {
+ mAppCompatAspectRatioState.mLetterboxBoundsForAspectRatio = bounds;
+ }
+
+ private boolean isParentFullscreenPortrait() {
+ final WindowContainer<?> parent = mActivityRecord.getParent();
+ return parent != null
+ && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+ && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ /**
+ * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
+ * made to outBounds.
+ *
+ * @return {@code true} if aspect ratio restrictions were applied.
+ */
+ private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
+ Rect containingBounds, float desiredAspectRatio) {
+ final float maxAspectRatio = getMaxAspectRatio();
+ final Task rootTask = mActivityRecord.getRootTask();
+ final Task task = mActivityRecord.getTask();
+ final float minAspectRatio = getMinAspectRatio();
+ final TaskFragment organizedTf = mActivityRecord.getOrganizedTaskFragment();
+ float aspectRatioToApply = desiredAspectRatio;
+ if (task == null || rootTask == null
+ || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
+ // Don't set aspect ratio if we are in VR mode.
+ || AppCompatUtils.isInVrUiMode(mActivityRecord.getConfiguration())
+ // TODO(b/232898850): Always respect aspect ratio requests.
+ // Don't set aspect ratio for activity in ActivityEmbedding split.
+ || (organizedTf != null && !organizedTf.fillsParent())) {
+ return false;
+ }
+
+ final int containingAppWidth = containingAppBounds.width();
+ final int containingAppHeight = containingAppBounds.height();
+ final float containingRatio = AppCompatUtils.computeAspectRatio(containingAppBounds);
+
+ if (aspectRatioToApply < 1) {
+ aspectRatioToApply = containingRatio;
+ }
+
+ if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
+ aspectRatioToApply = maxAspectRatio;
+ } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
+ aspectRatioToApply = minAspectRatio;
+ }
+
+ int activityWidth = containingAppWidth;
+ int activityHeight = containingAppHeight;
+
+ if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+ if (containingAppWidth < containingAppHeight) {
+ // Width is the shorter side, so we use that to figure-out what the max. height
+ // should be given the aspect ratio.
+ activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
+ } else {
+ // Height is the shorter side, so we use that to figure-out what the max. width
+ // should be given the aspect ratio.
+ activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
+ }
+ } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+ boolean adjustWidth;
+ switch (mActivityRecord.getRequestedConfigurationOrientation()) {
+ case ORIENTATION_LANDSCAPE:
+ // Width should be the longer side for this landscape app, so we use the width
+ // to figure-out what the max. height should be given the aspect ratio.
+ adjustWidth = false;
+ break;
+ case ORIENTATION_PORTRAIT:
+ // Height should be the longer side for this portrait app, so we use the height
+ // to figure-out what the max. width should be given the aspect ratio.
+ adjustWidth = true;
+ break;
+ default:
+ // This app doesn't have a preferred orientation, so we keep the length of the
+ // longer side, and use it to figure-out the length of the shorter side.
+ if (containingAppWidth < containingAppHeight) {
+ // Width is the shorter side, so we use the height to figure-out what the
+ // max. width should be given the aspect ratio.
+ adjustWidth = true;
+ } else {
+ // Height is the shorter side, so we use the width to figure-out what the
+ // max. height should be given the aspect ratio.
+ adjustWidth = false;
+ }
+ break;
+ }
+ if (adjustWidth) {
+ activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
+ } else {
+ activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
+ }
+ }
+
+ if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
+ // The display matches or is less than the activity aspect ratio, so nothing else to do.
+ return false;
+ }
+
+ // Compute configuration based on max or min supported width and height.
+ // Also account for the insets (e.g. display cutouts, navigation bar), which will be
+ // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out
+ // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise,
+ // the app bounds would end up too small. To achieve this we will also add clippable insets
+ // when the corresponding dimension fully fills the parent
+
+ int right = activityWidth + containingAppBounds.left;
+ int left = containingAppBounds.left;
+ if (right >= containingAppBounds.right) {
+ right = containingBounds.right;
+ left = containingBounds.left;
+ }
+ int bottom = activityHeight + containingAppBounds.top;
+ int top = containingAppBounds.top;
+ if (bottom >= containingAppBounds.bottom) {
+ bottom = containingBounds.bottom;
+ top = containingBounds.top;
+ }
+ outBounds.set(left, top, right, bottom);
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if the default aspect ratio for a letterboxed app in multi-window mode
+ * should be used.
+ */
+ private boolean isDefaultMultiWindowLetterboxAspectRatioDesired(
+ @NonNull Configuration parentConfig) {
+ final DisplayContent dc = mActivityRecord.mDisplayContent;
+ if (dc == null) {
+ return false;
+ }
+ final int windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+ return WindowConfiguration.inMultiWindowMode(windowingMode)
+ && !dc.getIgnoreOrientationRequest();
+ }
+
+ private static class AppCompatAspectRatioState {
+ // Whether the aspect ratio restrictions applied to the activity bounds
+ // in applyAspectRatio().
+ private boolean mIsAspectRatioApplied = false;
+
+ // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+ // aspect ratio. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+ @Nullable
+ private Rect mLetterboxBoundsForAspectRatio;
+ // Bounds populated in resolveFixedOrientationConfiguration when this activity is
+ // letterboxed for fixed orientation. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets. If
+ // letterboxed due to fixed orientation then aspect ratio restrictions are also respected.
+ // This happens when an activity has fixed orientation which doesn't match orientation of
+ // the parent because a display is ignoring orientation request or fixed to user rotation.
+ // See WindowManagerService#getIgnoreOrientationRequest and
+ // WindowManagerService#getFixedToUserRotation for more context.
+ @Nullable
+ private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+
+ @Nullable
+ Rect getLetterboxedContainerBounds() {
+ return mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+ : mLetterboxBoundsForAspectRatio;
+ }
+
+ void reset() {
+ mIsAspectRatioApplied = false;
+ mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mLetterboxBoundsForAspectRatio = null;
+ }
+
+ boolean isLetterboxedForFixedOrientationAndAspectRatio() {
+ return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
+ }
+
+ boolean isLetterboxedForAspectRatioOnly() {
+ return mLetterboxBoundsForAspectRatio != null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 16d3787..3eed96d 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,6 +33,8 @@
@NonNull
private final AppCompatOrientationPolicy mOrientationPolicy;
@NonNull
+ private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
+ @NonNull
private final AppCompatOverrides mAppCompatOverrides;
AppCompatController(@NonNull WindowManagerService wmService,
@@ -45,12 +47,9 @@
wmService.mLetterboxConfiguration);
mAppCompatOverrides = new AppCompatOverrides(activityRecord,
wmService.mLetterboxConfiguration, optPropBuilder);
- // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with aspectRatio.
- final LetterboxUiController tmpController = activityRecord.mLetterboxUiController;
- mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord,
- mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
- tmpController::shouldApplyUserMinAspectRatioOverride,
- tmpController::isSystemOverrideToFullscreenEnabled);
+ mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
+ mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
+ mTransparentPolicy, mAppCompatOverrides);
}
@NonNull
@@ -64,6 +63,11 @@
}
@NonNull
+ AppCompatAspectRatioPolicy getAppCompatAspectRatioPolicy() {
+ return mAppCompatAspectRatioPolicy;
+ }
+
+ @NonNull
AppCompatOverrides getAppCompatOverrides() {
return mAppCompatOverrides;
}
@@ -78,6 +82,11 @@
return mAppCompatOverrides.getAppCompatCameraOverrides();
}
+ @NonNull
+ AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
+ return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ }
+
@Nullable
AppCompatCameraPolicy getAppCompatCameraPolicy() {
if (mActivityRecord.mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 155e246..9bf8011 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -50,7 +50,6 @@
private final ActivityRecord mActivityRecord;
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
-
@NonNull
private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
@NonNull
@@ -109,7 +108,8 @@
mOrientationOverridesState.updateOrientationRequestLoopState();
return mOrientationOverridesState.shouldIgnoreRequestInLoop()
- && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio();
+ && !mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio();
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 69ba59b..17f0d97 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -32,8 +32,6 @@
import android.content.pm.ActivityInfo;
import android.util.Slog;
-import java.util.function.BooleanSupplier;
-
/**
* Contains all the logic related to orientation in the context of app compatibility
*/
@@ -46,24 +44,11 @@
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
- @NonNull
- private final BooleanSupplier mShouldApplyUserFullscreenOverride;
- @NonNull
- private final BooleanSupplier mShouldApplyUserMinAspectRatioOverride;
- @NonNull
- private final BooleanSupplier mIsSystemOverrideToFullscreenEnabled;
- // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with spectRatio component
AppCompatOrientationPolicy(@NonNull ActivityRecord activityRecord,
- @NonNull AppCompatOverrides appCompatOverrides,
- @NonNull BooleanSupplier shouldApplyUserFullscreenOverride,
- @NonNull BooleanSupplier shouldApplyUserMinAspectRatioOverride,
- @NonNull BooleanSupplier isSystemOverrideToFullscreenEnabled) {
+ @NonNull AppCompatOverrides appCompatOverrides) {
mActivityRecord = activityRecord;
mAppCompatOverrides = appCompatOverrides;
- mShouldApplyUserFullscreenOverride = shouldApplyUserFullscreenOverride;
- mShouldApplyUserMinAspectRatioOverride = shouldApplyUserMinAspectRatioOverride;
- mIsSystemOverrideToFullscreenEnabled = isSystemOverrideToFullscreenEnabled;
}
@ActivityInfo.ScreenOrientation
@@ -71,7 +56,9 @@
final DisplayContent displayContent = mActivityRecord.mDisplayContent;
final boolean isIgnoreOrientationRequestEnabled = displayContent != null
&& displayContent.getIgnoreOrientationRequest();
- if (mShouldApplyUserFullscreenOverride.getAsBoolean() && isIgnoreOrientationRequestEnabled
+ final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
+ .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
+ if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
@@ -88,8 +75,9 @@
// In some cases (e.g. Kids app) we need to map the candidate orientation to some other
// orientation.
candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
-
- if (mShouldApplyUserMinAspectRatioOverride.getAsBoolean() && (!isFixedOrientation(candidate)
+ final boolean shouldApplyUserMinAspectRatioOverride = mAppCompatOverrides
+ .getAppCompatAspectRatioOverrides().shouldApplyUserMinAspectRatioOverride();
+ if (shouldApplyUserMinAspectRatioOverride && (!isFixedOrientation(candidate)
|| candidate == SCREEN_ORIENTATION_LOCKED)) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
@@ -113,7 +101,9 @@
// mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
// which will always come first before this check as user override > device
// manufacturer override.
- if (mIsSystemOverrideToFullscreenEnabled.getAsBoolean() && isIgnoreOrientationRequestEnabled
+ final boolean isSystemOverrideToFullscreenEnabled = mAppCompatOverrides
+ .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
+ if (isSystemOverrideToFullscreenEnabled && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 94c6ba9..f6f93f9 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -18,20 +18,13 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -61,17 +54,13 @@
@NonNull
private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@NonNull
- private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp;
- @NonNull
private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
@NonNull
- private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp;
- @NonNull
- private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp;
- @NonNull
private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+ @NonNull
+ private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull LetterboxConfiguration letterboxConfiguration,
@@ -83,6 +72,11 @@
mLetterboxConfiguration, optPropBuilder);
mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
mLetterboxConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
+ mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
+ mLetterboxConfiguration, optPropBuilder,
+ activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
+ activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
mLetterboxConfiguration::isCompatFakeFocusEnabled);
@@ -101,16 +95,8 @@
== ORIENTATION_LANDSCAPE
);
- mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
mAllowForceResizeOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
- mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE,
- mLetterboxConfiguration::isUserAppAspectRatioSettingsEnabled);
- mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
- mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled);
}
@NonNull
@@ -123,6 +109,11 @@
return mAppCompatCameraOverrides;
}
+ @NonNull
+ AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
+ return mAppCompatAspectRatioOverrides;
+ }
+
/**
* Whether sending compat fake focus for split screen resumed activities is enabled. Needed
* because some game engines wait to get focus before drawing the content of the app which isn't
@@ -140,34 +131,10 @@
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS));
}
- boolean isSystemOverrideToFullscreenEnabled(int userAspectRatio) {
- return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
- && !mAllowOrientationOverrideOptProp.isFalse()
- && (userAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
- || userAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
- }
-
boolean isAllowOrientationOverrideOptOut() {
return mAllowOrientationOverrideOptProp.isFalse();
}
- /**
- * Whether we should apply the min aspect ratio per-app override. When this override is applied
- * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
- * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
- * apply if no value is provided in the manifest.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideMinAspectRatio() {
- return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
- }
-
boolean isOverrideRespectRequestedOrientationEnabled() {
return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
}
@@ -209,28 +176,6 @@
}
/**
- * Whether we should enable users to resize the current app.
- */
- boolean shouldEnableUserAspectRatioSettings() {
- // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
- // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
- // the current app doesn't opt-out so the first part of the predicate is true.
- return !mAllowUserAspectRatioOverrideOptProp.isFalse()
- && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
- && mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
- }
-
- boolean isUserFullscreenOverrideEnabled() {
- if (mAllowUserAspectRatioOverrideOptProp.isFalse()
- || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse()
- || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
- return false;
- }
- return true;
- }
-
- /**
* Whether we should apply the force non resize per-app override. When this override is applied
* it forces the packages it is applied to to be non-resizable.
*
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index be51dd3b..1b30a20 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -16,7 +16,12 @@
package com.android.server.wm;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
+
import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import java.util.function.BooleanSupplier;
@@ -48,4 +53,24 @@
}
};
}
+
+ /**
+ * Returns the aspect ratio of the given {@code rect}.
+ */
+ static float computeAspectRatio(Rect rect) {
+ final int width = rect.width();
+ final int height = rect.height();
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return Math.max(width, height) / (float) Math.min(width, height);
+ }
+
+ /**
+ * @param config The current {@link Configuration}
+ * @return {@code true} if using a VR headset.
+ */
+ static boolean isInVrUiMode(Configuration config) {
+ return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 44b414f..78636a7 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -98,7 +98,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -290,12 +289,10 @@
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.
- unfreezeEmbeddedChangingWindows();
- overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
- }
+ // No AE remote animation with Shell transition.
+ // Unfreeze the windows that were previously frozen for TaskFragment animation.
+ unfreezeEmbeddedChangingWindows();
+ overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
|| containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -726,64 +723,6 @@
}
/**
- * 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.
*/
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index a4fb959..54024e9 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -128,21 +128,24 @@
// TODO(b/263368846) Rename when ASM logic is moved in
@Retention(SOURCE)
- @IntDef({BAL_BLOCK,
- BAL_ALLOW_DEFAULT,
- BAL_ALLOW_ALLOWLISTED_UID,
+ @IntDef({
BAL_ALLOW_ALLOWLISTED_COMPONENT,
- BAL_ALLOW_VISIBLE_WINDOW,
+ BAL_ALLOW_ALLOWLISTED_UID,
+ BAL_ALLOW_BOUND_BY_FOREGROUND,
+ BAL_ALLOW_DEFAULT,
+ BAL_ALLOW_FOREGROUND,
+ BAL_ALLOW_GRACE_PERIOD,
BAL_ALLOW_PENDING_INTENT,
BAL_ALLOW_PERMISSION,
BAL_ALLOW_SAW_PERMISSION,
- BAL_ALLOW_GRACE_PERIOD,
- BAL_ALLOW_FOREGROUND,
- BAL_ALLOW_SDK_SANDBOX
+ BAL_ALLOW_SDK_SANDBOX,
+ BAL_ALLOW_TOKEN,
+ BAL_ALLOW_VISIBLE_WINDOW,
+ BAL_BLOCK
})
public @interface BalCode {}
- static final int BAL_BLOCK = 0;
+ static final int BAL_BLOCK = FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_BLOCKED;
static final int BAL_ALLOW_DEFAULT =
FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_DEFAULT;
@@ -195,10 +198,19 @@
static final int BAL_ALLOW_NON_APP_VISIBLE_WINDOW =
FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
+ /** Process belongs to a SDK sandbox */
+ static final int BAL_ALLOW_TOKEN =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_TOKEN;
+
+ /** Process belongs to a SDK sandbox */
+ static final int BAL_ALLOW_BOUND_BY_FOREGROUND =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_BOUND_BY_FOREGROUND;
+
static String balCodeToString(@BalCode int balCode) {
return switch (balCode) {
case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+ case BAL_ALLOW_BOUND_BY_FOREGROUND -> "BAL_ALLOW_BOUND_BY_FOREGROUND";
case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
@@ -207,6 +219,7 @@
case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+ case BAL_ALLOW_TOKEN -> "BAL_ALLOW_TOKEN";
case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
case BAL_BLOCK -> "BAL_BLOCK";
default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
@@ -1042,7 +1055,9 @@
|| balCode == BAL_ALLOW_PENDING_INTENT
|| balCode == BAL_ALLOW_SAW_PERMISSION
|| balCode == BAL_ALLOW_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_TOKEN
+ || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
return true;
}
}
@@ -1266,7 +1281,8 @@
|| balCode == BAL_ALLOW_PERMISSION
|| balCode == BAL_ALLOW_SAW_PERMISSION
|| balCode == BAL_ALLOW_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
return;
}
@@ -1572,7 +1588,7 @@
}
if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_FOREGROUND) {
+ || balCode == BAL_ALLOW_FOREGROUND || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask;
if (task != null && task.getDisplayArea() != null) {
joiner.add(prefix + "Tasks: ");
@@ -1659,6 +1675,8 @@
activityName = "";
}
writeBalAllowedLog(activityName, finalVerdict.getCode(), state);
+ } else {
+ writeBalAllowedLogMinimal(state);
}
} else {
@BalCode int code = finalVerdict.getCode();
@@ -1723,6 +1741,24 @@
);
}
+ @VisibleForTesting void writeBalAllowedLogMinimal(BalState state) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+ "",
+ BAL_ALLOW_DEFAULT,
+ NO_PROCESS_UID,
+ NO_PROCESS_UID,
+ state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
+ state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
+ state.callerExplicitOptInOrOut(),
+ state.mResultForRealCaller == null ? BAL_BLOCK
+ : state.mResultForRealCaller.getRawCode(),
+ state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
+ state.realCallerExplicitOptInOrOut(),
+ getTargetSdk(state.mCallingPackage),
+ getTargetSdk(state.mRealCallingPackage)
+ );
+ }
+
/**
* Called whenever an activity finishes. Stores the record, so it can be used by ASM grace
* period checks.
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 478524b..4a870a3 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -23,10 +23,13 @@
import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+import static com.android.window.flags.Flags.balImprovedMetrics;
import static java.util.Objects.requireNonNull;
@@ -110,8 +113,8 @@
}
// Allow if the flag was explicitly set.
if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
- "process allowed by token");
+ return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
+ /*background*/ true, "process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
@@ -120,7 +123,8 @@
? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
: isBoundByForegroundUid();
if (allowBoundByForegroundUid) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+ return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
+ : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 68a4172..2755a80 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -26,13 +26,14 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.window.flags.Flags;
/**
@@ -56,6 +57,9 @@
private boolean mIsCameraCompatTreatmentPending = false;
+ @Nullable
+ private Task mCameraTask;
+
CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent,
@NonNull CameraStateMonitor cameraStateMonitor,
@NonNull ActivityRefresher activityRefresher) {
@@ -116,6 +120,7 @@
final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
if (newCameraCompatMode != existingCameraCompatMode) {
mIsCameraCompatTreatmentPending = true;
+ mCameraTask = cameraActivity.getTask();
cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
.setFreeformCameraCompatMode(newCameraCompatMode);
forceUpdateActivityAndTask(cameraActivity);
@@ -127,18 +132,22 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
- if (isActivityForCameraIdRefreshing(cameraId)) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
- "Display id=%d is notified that Camera %s is closed but activity is"
- + " still refreshing. Rescheduling an update.",
- mDisplayContent.mDisplayId, cameraId);
- return false;
+ public boolean onCameraClosed(@NonNull String cameraId) {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = mCameraTask != null
+ ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
+ : null;
+ if (topActivity != null) {
+ if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
+ "Display id=%d is notified that Camera %s is closed but activity is"
+ + " still refreshing. Rescheduling an update.",
+ mDisplayContent.mDisplayId, cameraId);
+ return false;
+ }
}
- cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
- .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
- forceUpdateActivityAndTask(cameraActivity);
+ mCameraTask = null;
mIsCameraCompatTreatmentPending = false;
return true;
}
@@ -186,10 +195,9 @@
&& !activity.isEmbedded();
}
- private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (topActivity == null || !isTreatmentEnabledForActivity(topActivity)
+ private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
+ @NonNull String cameraId) {
+ if (!isTreatmentEnabledForActivity(topActivity)
|| mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index a54141c..068fc00 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -61,9 +61,6 @@
@NonNull
private final Handler mHandler;
- @Nullable
- private ActivityRecord mCameraActivity;
-
// Bi-directional map between package names and active camera IDs since we need to 1) get a
// camera id by a package name when resizing the window; 2) get a package name by a camera id
// when camera connection is closed and we need to clean up our records.
@@ -91,13 +88,13 @@
@Override
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
synchronized (mWmService.mGlobalLock) {
- notifyCameraOpened(cameraId, packageId);
+ notifyCameraOpenedWithDelay(cameraId, packageId);
}
}
@Override
public void onCameraClosed(@NonNull String cameraId) {
synchronized (mWmService.mGlobalLock) {
- notifyCameraClosed(cameraId);
+ notifyCameraClosedWithDelay(cameraId);
}
}
};
@@ -131,8 +128,8 @@
mCameraStateListeners.remove(listener);
}
- private void notifyCameraOpened(
- @NonNull String cameraId, @NonNull String packageName) {
+ private void notifyCameraOpenedWithDelay(@NonNull String cameraId,
+ @NonNull String packageName) {
// If an activity is restarting or camera is flipping, the camera connection can be
// quickly closed and reopened.
mScheduledToBeRemovedCameraIdSet.remove(cameraId);
@@ -142,25 +139,30 @@
// Some apps can’t handle configuration changes coming at the same time with Camera setup so
// delaying orientation update to accommodate for that.
mScheduledCompatModeUpdateCameraIdSet.add(cameraId);
- mHandler.postDelayed(
- () -> {
- synchronized (mWmService.mGlobalLock) {
- if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
- // Camera compat mode update has happened already or was cancelled
- // because camera was closed.
- return;
- }
- mCameraIdPackageBiMapping.put(packageName, cameraId);
- mCameraActivity = findCameraActivity(packageName);
- if (mCameraActivity == null || mCameraActivity.getTask() == null) {
- return;
- }
- notifyListenersCameraOpened(mCameraActivity, cameraId);
- }
- },
+ mHandler.postDelayed(() -> notifyCameraOpenedInternal(cameraId, packageName),
CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS);
}
+ private void notifyCameraOpenedInternal(@NonNull String cameraId, @NonNull String packageName) {
+ synchronized (mWmService.mGlobalLock) {
+ if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
+ // Camera compat mode update has happened already or was cancelled
+ // because camera was closed.
+ return;
+ }
+ mCameraIdPackageBiMapping.put(packageName, cameraId);
+ // If there are multiple activities of the same package name and none of
+ // them are the top running activity, we do not apply treatment (rather than
+ // guessing and applying it to the wrong activity).
+ final ActivityRecord cameraActivity =
+ findUniqueActivityWithPackageName(packageName);
+ if (cameraActivity == null || cameraActivity.getTask() == null) {
+ return;
+ }
+ notifyListenersCameraOpened(cameraActivity, cameraId);
+ }
+ }
+
private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
@NonNull String cameraId) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
@@ -174,7 +176,13 @@
}
}
- private void notifyCameraClosed(@NonNull String cameraId) {
+ /**
+ * Processes camera closed, and schedules notifying listeners.
+ *
+ * <p>The delay is introduced to avoid flickering when switching between front and back camera,
+ * and when an activity is refreshed due to camera compat treatment.
+ */
+ private void notifyCameraClosedWithDelay(@NonNull String cameraId) {
ProtoLog.v(WM_DEBUG_STATES,
"Display id=%d is notified that Camera %s is closed.",
mDisplayContent.mDisplayId, cameraId);
@@ -217,9 +225,10 @@
// Already reconnected to this camera, no need to clean up.
return;
}
- if (mCameraActivity != null && mCurrentListenerForCameraActivity != null) {
+
+ if (mCurrentListenerForCameraActivity != null) {
boolean closeSuccessful =
- mCurrentListenerForCameraActivity.onCameraClosed(mCameraActivity, cameraId);
+ mCurrentListenerForCameraActivity.onCameraClosed(cameraId);
if (closeSuccessful) {
mCameraIdPackageBiMapping.removeCameraId(cameraId);
mCurrentListenerForCameraActivity = null;
@@ -231,8 +240,14 @@
}
// TODO(b/335165310): verify that this works in multi instance and permission dialogs.
+ /**
+ * Finds a visible activity with the given package name.
+ *
+ * <p>If there are multiple visible activities with a given package name, and none of them are
+ * the `topRunningActivity`, returns null.
+ */
@Nullable
- private ActivityRecord findCameraActivity(@NonNull String packageName) {
+ private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) {
final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
/* considerKeyguardState= */ true);
if (topActivity != null && topActivity.packageName.equals(packageName)) {
@@ -277,11 +292,11 @@
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
/**
- * Notifies the compat listener that an activity has closed the camera.
+ * Notifies the compat listener that camera is closed.
*
* @return true if cleanup has been successful - the notifier might try again if false.
*/
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
- boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+ boolean onCameraClosed(@NonNull String cameraId);
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index efd5202..a38ac9b 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,14 +22,23 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -38,11 +47,14 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
+import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -173,6 +185,111 @@
mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
}
+ /**
+ * If necessary, override configuration fields related to app bounds.
+ * This will happen when the app is targeting SDK earlier than 35.
+ * The insets and configuration has decoupled since SDK level 35, to make the system
+ * compatible to existing apps, override the configuration with legacy metrics. In legacy
+ * metrics, fields such as appBounds will exclude some of the system bar areas.
+ * The override contains all potentially affected fields in Configuration, including
+ * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+ * All overrides to those fields should be in this method.
+ *
+ * TODO: Consider integrate this with computeConfigByResolveHint()
+ */
+ static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
+ Configuration newParentConfiguration, Configuration inOutConfig,
+ boolean optOutEdgeToEdge, boolean hasFixedRotationTransform,
+ boolean hasCompatDisplayInsets) {
+ if (displayContent == null) {
+ return;
+ }
+ final boolean useOverrideInsetsForConfig =
+ displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
+ ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ && !appInfo.isChangeEnabled(
+ OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
+ : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
+ final int parentWindowingMode =
+ newParentConfiguration.windowConfiguration.getWindowingMode();
+ final boolean isFloating = isFloating(parentWindowingMode)
+ // Check the requested windowing mode of activity as well in case it is
+ // switching between PiP and fullscreen.
+ && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
+ || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
+ rotation = displayContent.getRotation();
+ }
+ if (!optOutEdgeToEdge && (!useOverrideInsetsForConfig
+ || hasCompatDisplayInsets
+ || isFloating
+ || rotation == ROTATION_UNDEFINED)) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+ // Make sure the orientation related fields will be updated by the override insets, because
+ // fixed rotation has assigned the fields from display's configuration.
+ if (hasFixedRotationTransform) {
+ inOutConfig.windowConfiguration.setAppBounds(null);
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.orientation = ORIENTATION_UNDEFINED;
+ }
+
+ // Override starts here.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated
+ ? displayContent.mBaseDisplayHeight
+ : displayContent.mBaseDisplayWidth;
+ final int dh = rotated
+ ? displayContent.mBaseDisplayWidth
+ : displayContent.mBaseDisplayHeight;
+ final Rect nonDecorFrame = displayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorFrame;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(parentBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.intersect(nonDecorFrame);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ }
+ if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
+ displayContent.computeSizeRanges(info, rotated, dw, dh,
+ displayContent.getDisplayMetrics().density,
+ inOutConfig, true /* overrideConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT
+ : ORIENTATION_LANDSCAPE;
+ }
+ }
+
/** Returns {@code true} if requested override override configuration is not empty. */
boolean hasRequestedOverrideConfiguration() {
return mHasOverrideConfiguration;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a8aa0ba..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1754,6 +1754,82 @@
return mDisplayRotation.updateOrientation(orientation, forceUpdate);
}
+ @Nullable
+ private ActivityRecord getLastOrientationSourceApp() {
+ final WindowContainer<?> orientationSrc = getLastOrientationSource();
+ return orientationSrc != null ? orientationSrc.asActivityRecord() : null;
+ }
+
+ /**
+ * This is called when the display rotation is changed. If the current top activity which
+ * decides the display orientation is not opaque, all non-top visible activities with
+ * different fixed orientations will still keep their original appearances.
+ */
+ void applyFixedRotationForNonTopVisibleActivityIfNeeded() {
+ if (!mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ return;
+ }
+ final ActivityRecord orientationSrcApp = getLastOrientationSourceApp();
+ if (orientationSrcApp == null || orientationSrcApp.fillsParent()) {
+ return;
+ }
+ final int topOrientation = orientationSrcApp.getRequestedOrientation();
+ if (topOrientation == SCREEN_ORIENTATION_UNSPECIFIED) {
+ return;
+ }
+ forAllActivities(ar -> {
+ if (!ar.isVisibleRequested()) {
+ return true;
+ }
+ applyFixedRotationForNonTopVisibleActivityIfNeeded(ar, topOrientation);
+ return false;
+ }, true /* traverseTopToBottom */);
+ }
+
+ /**
+ * This is called when a non-top activity becomes visible. Such when moving a task which
+ * contains 2 activities with different fixed orientations, and if the top one is translucent,
+ * then the bottom one will apply the fixed rotation transform for its orientation.
+ */
+ void applyFixedRotationForNonTopVisibleActivityIfNeeded(@NonNull ActivityRecord ar) {
+ if (!mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ return;
+ }
+ final ActivityRecord orientationSrcApp = getLastOrientationSourceApp();
+ if (orientationSrcApp != null) {
+ applyFixedRotationForNonTopVisibleActivityIfNeeded(ar,
+ orientationSrcApp.getRequestedOrientation());
+ }
+ }
+
+ /**
+ * If the given visible activity uses a different fixed orientation than the current top, the
+ * fixed rotation transform will be applied to respect its requested appearance.
+ */
+ private void applyFixedRotationForNonTopVisibleActivityIfNeeded(@NonNull ActivityRecord ar,
+ @ActivityInfo.ScreenOrientation int topOrientation) {
+ final int orientation = ar.getRequestedOrientation();
+ if (orientation == topOrientation || ar.inMultiWindowMode()
+ || ar.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
+ return;
+ }
+ final int displayRotation = getRotation();
+ final int rotation = ar.isVisible()
+ ? ar.getWindowConfiguration().getDisplayRotation()
+ : mDisplayRotation.rotationForOrientation(orientation, displayRotation);
+ if (rotation == displayRotation) {
+ return;
+ }
+ startFixedRotationTransform(ar, rotation);
+ final WindowState wallpaperTarget = mWallpaperController.getWallpaperTarget();
+ if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == ar) {
+ final WindowState wp = mWallpaperController.getTopVisibleWallpaper();
+ if (wp != null) {
+ wp.mToken.linkFixedRotationTransform(ar);
+ }
+ }
+ }
+
@Override
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
// Do not consider children because if they are requested to be synced, they should be
@@ -1822,10 +1898,23 @@
return false;
}
if (r.hasFixedRotationTransform()) {
+ if (mWmService.mFlags.mRespectNonTopVisibleFixedOrientation
+ && mFixedRotationLaunchingApp == null) {
+ // It could be finishing the previous top translucent activity, and the next fixed
+ // orientation activity becomes the current top.
+ setFixedRotationLaunchingAppUnchecked(r,
+ r.getWindowConfiguration().getDisplayRotation());
+ }
// It has been set and not yet finished.
return true;
}
- if (!r.occludesParent() || r.isReportedDrawn()) {
+ if (mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ if (r.isReportedDrawn()) {
+ // It is late for a drawn app. Either this is already a stable state or it needs
+ // a rotation animation to handle the change.
+ return false;
+ }
+ } else if (!r.occludesParent() || r.isReportedDrawn()) {
// While entering or leaving a translucent or floating activity (e.g. dialog style),
// there is a visible activity in the background. Then it still needs rotation animation
// to cover the activity configuration change.
@@ -3993,12 +4082,12 @@
final Transaction t = mWmService.mTransactionFactory.get();
forAllWindows(w -> {
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController == null) {
+ if (wsa.mSurfaceControl == null) {
return;
}
if (!mWmService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mToken
+ " pid=" + w.mSession.mPid
+ " uid=" + w.mSession.mUid);
@@ -4007,7 +4096,7 @@
mTmpWindow = w;
} else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mActivityRecord);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
wsa.destroySurface(t);
@@ -6923,7 +7012,11 @@
// In most cases this is a no-op if the activity doesn't have fixed rotation.
// Otherwise it could be from finishing recents animation while the display has
// different orientation.
- r.finishFixedRotationTransform();
+ if (!mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ r.finishFixedRotationTransform();
+ } else if (!r.isVisible()) {
+ r.finishFixedRotationTransform();
+ }
return;
}
if (mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) {
@@ -6938,7 +7031,12 @@
// E.g. activity A transferred starting window to B, only A will receive transition
// finished event. A doesn't have fixed rotation but B is the rotated launching app.
final Task task = r.getTask();
- if (task == null || task != mFixedRotationLaunchingApp.getTask()) {
+ if (task != mFixedRotationLaunchingApp.getTask()
+ // When closing a translucent task A (r.fillsParent() is false) to a
+ // visible task B, because only A has visibility change, there is only A's
+ // transition callback. Then it still needs to update orientation for B.
+ && (!mWmService.mFlags.mRespectNonTopVisibleFixedOrientation
+ || r.fillsParent())) {
// Different tasks won't be in one activity transition animation.
return;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c67928a..d2976b0 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -627,6 +627,7 @@
mRotation = rotation;
+ mDisplayContent.applyFixedRotationForNonTopVisibleActivityIfNeeded();
mDisplayContent.setLayoutNeeded();
mDisplayContent.mWaitingForConfig = true;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 9998e1a..1a0124a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -342,12 +342,19 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public boolean onCameraClosed(@NonNull String cameraId) {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null) {
+ return true;
+ }
+
synchronized (this) {
// TODO(b/336474959): Once refresh is implemented in `CameraCompatFreeformPolicy`,
// consider checking this in CameraStateMonitor before notifying the listeners (this).
- if (isActivityForCameraIdRefreshing(cameraId)) {
+ if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that camera is closed but activity is"
+ " still refreshing. Rescheduling an update.",
@@ -355,15 +362,15 @@
return false;
}
}
+
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera is closed, updating rotation.",
mDisplayContent.mDisplayId);
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (topActivity == null
- // Checking whether an activity in fullscreen rather than the task as this
- // camera compat treatment doesn't cover activity embedding.
- || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // Checking whether an activity in fullscreen rather than the task as this camera compat
+ // treatment doesn't cover activity embedding.
+ // TODO(b/350495350): Consider checking whether this activity is the camera activity, or
+ // whether the top activity has the same task as the one which opened camera.
+ if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return true;
}
recomputeConfigurationForCameraCompatIfNeeded(topActivity);
@@ -372,14 +379,13 @@
}
// TODO(b/336474959): Do we need cameraId here?
- private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
- final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
- /* considerKeyguardState= */ true);
- if (!isTreatmentEnabledForActivity(topActivity)
- || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
+ private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord activity,
+ @NonNull String cameraId) {
+ if (!isTreatmentEnabledForActivity(activity)
+ || !mCameraStateMonitor.isCameraWithIdRunningForActivity(activity, cameraId)) {
return false;
}
- return mActivityRefresher.isActivityRefreshing(topActivity);
+ return mActivityRefresher.isActivityRefreshing(activity);
}
private void recomputeConfigurationForCameraCompatIfNeeded(
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index f40eb24..3123018 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -174,6 +174,9 @@
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != mStarting && mNotifyClients) {
+ if (!isTop) {
+ r.mDisplayContent.applyFixedRotationForNonTopVisibleActivityIfNeeded(r);
+ }
r.ensureActivityConfiguration(true /* ignoreVisibility */);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
// occlusion detection depending on the type or if it's a trusted overlay.
populateOverlayInputInfo(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
return;
}
// Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
if (w.mWinAnimator.hasSurface()) {
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index b5af806..0161ae5 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,7 +19,6 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import android.annotation.DimenRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,11 +28,11 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.utils.DimenPxIntSupplier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Function;
-import java.util.function.IntSupplier;
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
@@ -308,34 +307,6 @@
// Flags dynamically updated with {@link android.provider.DeviceConfig}.
@NonNull private final SynchedDeviceConfig mDeviceConfig;
- // Cached version of IntSupplier customised to evaluate new dimen in pixels
- // when density changes
- private static class DimenPxIntSupplier implements IntSupplier {
-
- @NonNull
- private final Context mContext;
-
- private final int mResourceId;
-
- private float mLastDensity = Float.MIN_VALUE;
- private int mValue = 0;
-
- private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
- mContext = context;
- mResourceId = resourceId;
- }
-
- @Override
- public int getAsInt() {
- final float newDensity = mContext.getResources().getDisplayMetrics().density;
- if (newDensity != mLastDensity) {
- mLastDensity = newDensity;
- mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
- }
- return mValue;
- }
- }
-
LetterboxConfiguration(@NonNull final Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(
() -> readLetterboxHorizontalReachabilityPositionFromConfig(
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a3550bc..235d6cd 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,15 +17,6 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
-import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -44,7 +35,6 @@
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
-import static com.android.server.wm.ActivityRecord.computeAspectRatio;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
@@ -54,23 +44,18 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
-import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Slog;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -80,7 +65,6 @@
import android.view.WindowInsets;
import android.view.WindowManager;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
@@ -103,11 +87,6 @@
private boolean mShowWallpaperForLetterboxBackground;
- // TODO(b/315140179): Make mUserAspectRatio final
- // The min aspect ratio override set by user
- @PackageManager.UserMinAspectRatio
- private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
-
@Nullable
private Letterbox mLetterbox;
@@ -154,22 +133,6 @@
}
/**
- * Whether we should apply the min aspect ratio per-app override. When this override is applied
- * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
- * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
- * apply if no value is provided in the manifest.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideMinAspectRatio() {
- return getAppCompatOverrides().shouldOverrideMinAspectRatio();
- }
-
- /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -230,10 +193,6 @@
return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
}
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
- }
-
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
@@ -395,7 +354,7 @@
// be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
// actually fullscreen. If display is still in transition e.g. unfolding, don't return true
// for HALF_FOLDED state or app will flicker.
- private boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
+ boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
Task task = mActivityRecord.getTask();
return mActivityRecord.mDisplayContent != null && task != null
&& mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(
@@ -444,48 +403,14 @@
}
float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
- return shouldUseSplitScreenAspectRatio(parentConfiguration)
- ? getSplitScreenAspectRatio()
- : mActivityRecord.shouldCreateCompatDisplayInsets()
- ? getDefaultMinAspectRatioForUnresizableApps()
- : getDefaultMinAspectRatio();
+ return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .getFixedOrientationLetterboxAspectRatio(parentConfiguration);
}
boolean isLetterboxEducationEnabled() {
return mLetterboxConfiguration.getIsEducationEnabled();
}
- private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
- final boolean isBookMode = isDisplayFullScreenAndInPosture(/* isTabletop */ false);
- final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier(
- parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
- final boolean isTabletopMode = isDisplayFullScreenAndInPosture(/* isTabletop */ true);
- final boolean isLandscape = isFixedOrientationLandscape(
- mActivityRecord.getOverrideOrientation());
- final AppCompatCameraOverrides appCompatCameraOverrides =
- mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
- final AppCompatCameraPolicy cameraPolicy =
- mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
- final boolean isCameraCompatTreatmentActive = cameraPolicy != null
- && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
- // Don't resize to split screen size when in book mode if letterbox position is centered
- return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
- || (appCompatCameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
- && isCameraCompatTreatmentActive);
- }
-
- private float getDefaultMinAspectRatioForUnresizableApps() {
- if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
- || mActivityRecord.getDisplayArea() == null) {
- return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
- > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
- ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
- : getDefaultMinAspectRatio();
- }
-
- return getSplitScreenAspectRatio();
- }
-
/**
* @return {@value true} if the resulting app is letterboxed in a way defined as thin.
*/
@@ -547,123 +472,9 @@
return !isHorizontalThinLetterboxed();
}
- float getSplitScreenAspectRatio() {
- // Getting the same aspect ratio that apps get in split screen.
- final DisplayArea displayArea = mActivityRecord.getDisplayArea();
- if (displayArea == null) {
- return getDefaultMinAspectRatioForUnresizableApps();
- }
- int dividerWindowWidth =
- getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
- int dividerInsets =
- getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
- int dividerSize = dividerWindowWidth - dividerInsets * 2;
- final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
- if (bounds.width() >= bounds.height()) {
- bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
- bounds.right = bounds.centerX();
- } else {
- bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
- bounds.bottom = bounds.centerY();
- }
- return computeAspectRatio(bounds);
- }
-
- /**
- * Whether we should enable users to resize the current app.
- */
- boolean shouldEnableUserAspectRatioSettings() {
- return getAppCompatOverrides().shouldEnableUserAspectRatioSettings();
- }
-
- /**
- * Whether we should apply the user aspect ratio override to the min aspect ratio for the
- * current app.
- */
- boolean shouldApplyUserMinAspectRatioOverride() {
- if (!shouldEnableUserAspectRatioSettings()) {
- return false;
- }
-
- mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
- return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
- && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
- && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
- }
-
- boolean shouldApplyUserFullscreenOverride() {
- if (isUserFullscreenOverrideEnabled()) {
- mUserAspectRatio = getUserMinAspectRatioOverrideCode();
-
- return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
- }
-
- return false;
- }
-
- boolean isUserFullscreenOverrideEnabled() {
- return getAppCompatOverrides().isUserFullscreenOverrideEnabled();
- }
-
- boolean isSystemOverrideToFullscreenEnabled() {
- return getAppCompatOverrides().isSystemOverrideToFullscreenEnabled(mUserAspectRatio);
- }
-
- boolean hasFullscreenOverride() {
- // `mUserAspectRatio` is always initialized first in `shouldApplyUserFullscreenOverride()`.
- return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled();
- }
-
- float getUserMinAspectRatio() {
- switch (mUserAspectRatio) {
- case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
- return getDisplaySizeMinAspectRatio();
- case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
- return getSplitScreenAspectRatio();
- case USER_MIN_ASPECT_RATIO_16_9:
- return 16 / 9f;
- case USER_MIN_ASPECT_RATIO_4_3:
- return 4 / 3f;
- case USER_MIN_ASPECT_RATIO_3_2:
- return 3 / 2f;
- default:
- throw new AssertionError("Unexpected user min aspect ratio override: "
- + mUserAspectRatio);
- }
- }
-
- @VisibleForTesting
- int getUserMinAspectRatioOverrideCode() {
- try {
- return mActivityRecord.mAtmService.getPackageManager()
- .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
- }
- return mUserAspectRatio;
- }
-
- private float getDisplaySizeMinAspectRatio() {
- final DisplayArea displayArea = mActivityRecord.getDisplayArea();
- if (displayArea == null) {
- return mActivityRecord.info.getMinAspectRatio();
- }
- final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
- return computeAspectRatio(bounds);
- }
-
- private float getDefaultMinAspectRatio() {
- if (mActivityRecord.getDisplayArea() == null
- || !mLetterboxConfiguration
- .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
- return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
- }
- return getDisplaySizeMinAspectRatio();
- }
-
- Resources getResources() {
- return mActivityRecord.mWmService.mContext.getResources();
+ boolean shouldOverrideMinAspectRatio() {
+ return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio();
}
@LetterboxConfiguration.LetterboxVerticalReachabilityPosition
@@ -1086,7 +897,7 @@
pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin));
pw.println(prefix + " activityAspectRatio="
- + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds()));
+ + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds()));
boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin);
pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi);
@@ -1145,13 +956,15 @@
if (mActivityRecord.inSizeCompatMode()) {
return "SIZE_COMPAT_MODE";
}
- if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
+ if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()) {
return "FIXED_ORIENTATION";
}
if (mainWin.isLetterboxedForDisplayCutout()) {
return "DISPLAY_CUTOUT";
}
- if (mActivityRecord.isLetterboxedForAspectRatioOnly()) {
+ if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForAspectRatioOnly()) {
return "ASPECT_RATIO";
}
return "UNKNOWN_REASON";
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index a8edaeb..f8665c7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -44,8 +44,8 @@
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.util.FastPrintWriter;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -53,7 +53,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.function.Consumer;
/**
* Helper class to run app animations in a remote process.
@@ -349,10 +348,6 @@
} finally {
mIsFinishing = false;
}
- // Reset input for all activities when the remote animation is finished.
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(false);
- mDisplayContent.forAllActivities(updateActivities);
}
setRunningRemoteAnimation(false);
ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+ final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
return;
}
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
// app to request another one.
Slog.w(TAG_WM,
"Looks like we have reclaimed some memory, clearing surface for retry.");
- if (surfaceController != null) {
+ if (surfaceControl != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
"SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
return;
}
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+ t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
}, false);
if (!skipScreenshot) {
// Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..f5108f5b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- /** Set of visible alert/app-overlay window surfaces connected to this session. */
- private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+ /** Set of visible alert/app-overlay windows connected to this session. */
+ private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
boolean mCanForceShowingInsets;
@@ -769,9 +769,8 @@
return !mAddedWindows.isEmpty();
}
- void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
- boolean visible, int type) {
-
+ void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+ final int type = window.mAttrs.type;
if (!isSystemAlertWindowType(type)) {
return;
}
@@ -782,7 +781,7 @@
final boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
+ changed = mAlertWindows.add(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
} else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
+ changed = mAlertWindows.remove(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
}
if (changed && noSystemOverlayPermission) {
- if (mAlertWindowSurfaces.isEmpty()) {
+ if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
if (changed && mPid != WindowManagerService.MY_PID) {
// Notify activity manager that the process contains overlay/alert windows, so it can
// adjust the importance score for the process.
- setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+ setHasOverlayUi(!mAlertWindows.isEmpty());
}
}
@@ -859,7 +858,7 @@
}
mSurfaceSession = null;
mAddedWindows.clear();
- mAlertWindowSurfaces.clear();
+ mAlertWindows.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
@@ -880,7 +879,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+ pw.print(" mAlertWindows="); pw.print(mAlertWindows);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
/** @return {@code true} if there is an alert window surface on the given display. */
boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
- for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
- final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
- if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mAlertWindows.valueAt(i);
+ if (window.mDisplayContent == displayContent) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 79fec23..74f8a068 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -816,42 +816,30 @@
}
}
- /** Convenience method to reparent a task to the top or bottom position of the root task. */
- boolean reparent(Task preferredRootTask, boolean toTop,
- @ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, boolean deferResume,
- String reason) {
- return reparent(preferredRootTask, toTop ? MAX_VALUE : 0, moveRootTaskMode, animate,
- deferResume, true /* schedulePictureInPictureModeChange */, reason);
- }
-
/**
* Reparents the task into a preferred root task, creating it if necessary.
*
* @param preferredRootTask the target root task to move this task
- * @param position the position to place this task in the new root task
+ * @param toTop top or bottom position to place this task in the target task
* @param animate whether or not we should wait for the new window created as a part of the
* reparenting to be drawn and animated in
* @param moveRootTaskMode whether or not to move the root task to the front always, only if
* it was previously focused & in front, or never
* @param deferResume whether or not to update the visibility of other tasks and root tasks
* that may have changed as a result of this reparenting
- * @param schedulePictureInPictureModeChange specifies whether or not to schedule the PiP mode
- * change. Callers may set this to false if they are explicitly scheduling PiP mode
- * changes themselves, like during the PiP animation
* @param reason the caller of this reparenting
* @return whether the task was reparented
*/
// TODO: Inspect all call sites and change to just changing windowing mode of the root task vs.
// re-parenting the task. Can only be done when we are no longer using static root task Ids.
- boolean reparent(Task preferredRootTask, int position,
+ boolean reparent(Task preferredRootTask, boolean toTop,
@ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, boolean deferResume,
- boolean schedulePictureInPictureModeChange, String reason) {
+ String reason) {
final ActivityTaskSupervisor supervisor = mTaskSupervisor;
final RootWindowContainer root = mRootWindowContainer;
- final WindowManagerService windowManager = mAtmService.mWindowManager;
final Task sourceRootTask = getRootTask();
final Task toRootTask = supervisor.getReparentTargetRootTask(this, preferredRootTask,
- position == MAX_VALUE);
+ toTop);
if (toRootTask == sourceRootTask) {
return false;
}
@@ -862,7 +850,6 @@
final ActivityRecord topActivity = getTopNonFinishingActivity();
mAtmService.deferWindowLayout();
- boolean kept = true;
try {
final ActivityRecord r = topRunningActivityLocked();
final boolean wasFocused = r != null && root.isTopDisplayFocusedRootTask(sourceRootTask)
@@ -878,12 +865,7 @@
|| (moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT
&& (wasFocused || wasFront));
- reparent(toRootTask, position, moveRootTaskToFront, reason);
-
- if (schedulePictureInPictureModeChange) {
- // Notify of picture-in-picture mode changes
- supervisor.scheduleUpdatePictureInPictureModeIfNeeded(this, sourceRootTask);
- }
+ reparent(toRootTask, toTop ? MAX_VALUE : 0, moveRootTaskToFront, reason);
// If the task had focus before (or we're requested to move focus), move focus to the
// new root task by moving the root task to the front.
@@ -3424,9 +3406,11 @@
appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.isUserFullscreenOverrideEnabled = top != null
- && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
+ && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride();
appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top != null
- && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
+ && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
&& top.mLetterboxUiController.isFromDoubleTap();
if (top != null) {
@@ -3460,7 +3444,8 @@
}
appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null
&& !appCompatTaskInfo.topActivityInSizeCompat
- && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings()
+ && top.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings()
&& !info.isTopActivityTransparent;
appCompatTaskInfo.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top == null
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index b6b6cf2..561ff7d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -46,7 +46,6 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
@@ -58,8 +57,8 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
@@ -146,13 +145,6 @@
private final boolean mIsSystemOrganizer;
/**
- * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
- * organized by this organizer.
- */
- @Nullable
- private RemoteAnimationDefinition mRemoteAnimationDefinition;
-
- /**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
* {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
* wait until the organizer finished handling the {@link TaskFragmentTransaction}.
@@ -533,50 +525,6 @@
}
@Override
- public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
- @NonNull RemoteAnimationDefinition definition) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- synchronized (mGlobalLock) {
- ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
- "Register remote animations for organizer=%s uid=%d pid=%d",
- organizer.asBinder(), uid, pid);
- final TaskFragmentOrganizerState organizerState =
- mTaskFragmentOrganizerState.get(organizer.asBinder());
- if (organizerState == null) {
- throw new IllegalStateException("The organizer hasn't been registered.");
- }
- if (organizerState.mRemoteAnimationDefinition != null) {
- throw new IllegalStateException(
- "The organizer has already registered remote animations="
- + organizerState.mRemoteAnimationDefinition);
- }
-
- definition.setCallingPidUid(pid, uid);
- organizerState.mRemoteAnimationDefinition = definition;
- }
- }
-
- @Override
- public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
- final int pid = Binder.getCallingPid();
- final long uid = Binder.getCallingUid();
- synchronized (mGlobalLock) {
- ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
- "Unregister remote animations for organizer=%s uid=%d pid=%d",
- organizer.asBinder(), uid, pid);
- final TaskFragmentOrganizerState organizerState =
- mTaskFragmentOrganizerState.get(organizer.asBinder());
- if (organizerState == null) {
- Slog.e(TAG, "The organizer hasn't been registered.");
- return;
- }
-
- organizerState.mRemoteAnimationDefinition = null;
- }
- }
-
- @Override
public void onTransactionHandled(@NonNull IBinder transactionToken,
@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
@@ -617,25 +565,6 @@
}
}
- /**
- * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
- * {@code null} if it doesn't.
- */
- @Nullable
- public RemoteAnimationDefinition getRemoteAnimationDefinition(
- @NonNull ITaskFragmentOrganizer organizer) {
- synchronized (mGlobalLock) {
- final TaskFragmentOrganizerState organizerState =
- mTaskFragmentOrganizerState.get(organizer.asBinder());
- if (organizerState == null) {
- Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
- + " to play animation on its organized windows.");
- return null;
- }
- return organizerState.mRemoteAnimationDefinition;
- }
- }
-
int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
@@ -1240,16 +1169,6 @@
}
}
- @Override
- public boolean isActivityEmbedded(IBinder activityToken) {
- synchronized (mGlobalLock) {
- final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
- return activity != null
- ? activity.isEmbeddedInHostContainer()
- : false;
- }
- }
-
@VisibleForTesting
@NonNull
IApplicationThread getAppThread(int pid, int uid) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 47af6fc..5336044 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2126,6 +2126,16 @@
}
/**
+
+ * Wallpaper will set itself as target if it wants to keep itself visible without a target.
+ */
+ private static boolean wallpaperIsOwnTarget(WallpaperWindowToken wallpaper) {
+ final WindowState target =
+ wallpaper.getDisplayContent().mWallpaperController.getWallpaperTarget();
+ return target != null && target.isDescendantOf(wallpaper);
+ }
+
+ /**
* Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
*/
private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
@@ -2133,8 +2143,13 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+ if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
+ || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
wallpaper.commitVisibility(showWallpaper);
+ } else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
+ && !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
+ && !wallpaperIsOwnTarget(wallpaper)) {
+ wallpaper.setVisibleRequested(false);
}
if (showWallpaper && Flags.ensureWallpaperInTransitions()
&& wallpaper.isVisibleRequested()
@@ -2556,11 +2571,10 @@
if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
- // Reject no-ops, unless wallpaper
- if (!changeInfo.hasChanged()
- && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Rejecting as no-op: %s", wc);
+ " Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested());
continue;
}
targets.add(changeInfo);
@@ -2757,12 +2771,19 @@
return out;
}
+ // Get the animation theme from the top-most application window
+ // when Flags.customAnimationsBehindTranslucent() is false.
final AnimationOptions animOptionsForActivityTransition =
calculateAnimationOptionsForActivityTransition(type, sortedTargets);
+
if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
out.setAnimationOptions(animOptionsForActivityTransition);
}
+ // Store the animation options of the topmost non-translucent change
+ // (Used when Flags.customAnimationsBehindTranslucent() is true)
+ AnimationOptions activityAboveAnimationOptions = null;
+
final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
@@ -2881,9 +2902,26 @@
change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
}
- AnimationOptions animOptions = null;
+ // Calculate the animation options for this change
if (Flags.moveAnimationOptionsToChange()) {
- if (activityRecord != null && animOptionsForActivityTransition != null) {
+ AnimationOptions animOptions = null;
+ if (Flags.customAnimationsBehindTranslucent() && activityRecord != null) {
+ if (activityAboveAnimationOptions != null) {
+ // Inherit the options from one of the changes on top of this
+ animOptions = activityAboveAnimationOptions;
+ } else {
+ // Create the options based on this change's custom animations and layout
+ // parameters
+ animOptions = getOptions(activityRecord /* customAnimActivity */,
+ activityRecord /* animLpActivity */);
+ if (!change.hasFlags(FLAG_TRANSLUCENT)) {
+ // If this change is not translucent, its options are going to be
+ // inherited by the changes below
+ activityAboveAnimationOptions = animOptions;
+ }
+ }
+ } else if (activityRecord != null && animOptionsForActivityTransition != null) {
+ // Use the same options from the top activity for all the activities
animOptions = animOptionsForActivityTransition;
} else if (Flags.activityEmbeddingOverlayPresentationFlag()
&& isEmbeddedTaskFragment) {
@@ -2931,25 +2969,42 @@
@Nullable
private static AnimationOptions calculateAnimationOptionsForActivityTransition(
@TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) {
- TransitionInfo.AnimationOptions animOptions = null;
-
- // Check if the top-most app is an activity (ie. activity->activity). If so, make sure
- // to honor its custom transition options.
WindowContainer<?> topApp = null;
for (int i = 0; i < sortedTargets.size(); i++) {
- if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
- topApp = sortedTargets.get(i).mContainer;
- break;
+ if (!isWallpaper(sortedTargets.get(i).mContainer)) {
+ topApp = sortedTargets.get(i).mContainer;
+ break;
+ }
}
- if (topApp.asActivityRecord() != null) {
- final ActivityRecord topActivity = topApp.asActivityRecord();
- animOptions = addCustomActivityTransition(topActivity, true/* open */,
- null /* animOptions */);
- animOptions = addCustomActivityTransition(topActivity, false/* open */,
+ ActivityRecord animLpActivity = findAnimLayoutParamsActivityRecord(type, sortedTargets);
+ return getOptions(topApp.asActivityRecord() /* customAnimActivity */,
+ animLpActivity /* animLpActivity */);
+ }
+
+ /**
+ * Updates and returns animOptions with the layout parameters of animLpActivity
+ * @param customAnimActivity the activity that drives the custom animation options
+ * @param animLpActivity the activity that drives the animation options with its layout
+ * parameters
+ * @return the options extracted from the provided activities
+ */
+ @Nullable
+ private static AnimationOptions getOptions(@Nullable ActivityRecord customAnimActivity,
+ @Nullable ActivityRecord animLpActivity) {
+ AnimationOptions animOptions = null;
+ // Custom
+ if (customAnimActivity != null) {
+ animOptions = addCustomActivityTransition(customAnimActivity, true /* open */,
+ animOptions);
+ animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
animOptions);
}
- final WindowManager.LayoutParams animLp =
- getLayoutParamsForAnimationsStyle(type, sortedTargets);
+
+ // Layout parameters
+ final WindowState mainWindow = animLpActivity != null
+ ? animLpActivity.findMainWindow() : null;
+ final WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null;
+
if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
&& animLp.windowAnimations != 0) {
// Don't send animation options if no windowAnimations have been set or if the we
@@ -3087,10 +3142,9 @@
return ancestor;
}
- private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
- ArrayList<ChangeInfo> sortedTargets) {
- // Find the layout params of the top-most application window that is part of the
- // transition, which is what will control the animation theme.
+ @Nullable
+ private static ActivityRecord findAnimLayoutParamsActivityRecord(
+ @TransitionType int transit, @NonNull List<ChangeInfo> sortedTargets) {
final ArraySet<Integer> activityTypes = new ArraySet<>();
final int targetCount = sortedTargets.size();
for (int i = 0; i < targetCount; ++i) {
@@ -3110,16 +3164,7 @@
// activity through the layout parameter animation style.
return null;
}
- final ActivityRecord animLpActivity =
- findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
- final WindowState mainWindow = animLpActivity != null
- ? animLpActivity.findMainWindow() : null;
- return mainWindow != null ? mainWindow.mAttrs : null;
- }
- private static ActivityRecord findAnimLayoutParamsActivityRecord(
- List<ChangeInfo> sortedTargets,
- @TransitionType int transit, ArraySet<Integer> activityTypes) {
// Remote animations always win, but fullscreen windows override non-fullscreen windows.
ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
w -> w.getRemoteAnimationDefinition() != null
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 3044abd..cdb14ab 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -162,10 +162,6 @@
mTransparentPolicyState.clearInheritedCompatDisplayInsets();
}
- TransparentPolicyState getTransparentPolicyState() {
- return mTransparentPolicyState;
- }
-
/**
* In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
* activity beneath using the given consumer and returns {@code true}.
@@ -176,7 +172,7 @@
@NonNull
Optional<ActivityRecord> getFirstOpaqueActivity() {
- return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity)
+ return isRunning() ? Optional.ofNullable(mTransparentPolicyState.mFirstOpaqueActivity)
: Optional.empty();
}
@@ -216,10 +212,6 @@
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
}
- private void inheritConfiguration(ActivityRecord firstOpaque) {
- mTransparentPolicyState.inheritFromOpaque(firstOpaque);
- }
-
/**
* Encapsulate the state for the current translucent activity when the transparent policy
* has started.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d02f1c..eb1a80b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -117,6 +117,7 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.AlwaysTruePredicate;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -458,6 +459,9 @@
source.setFrame(provider.getArbitraryRectangle())
.updateSideHint(getBounds())
.setBoundingRects(provider.getBoundingRects());
+ if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ source.setFlags(provider.getFlags());
+ }
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
@@ -2579,8 +2583,12 @@
// Containers don't belong to the same hierarchy???
if (commonAncestor == null) {
- throw new IllegalArgumentException("No in the same hierarchy this="
- + thisParentChain + " other=" + otherParentChain);
+ final int thisZ = getPrefixOrderIndex();
+ final int otherZ = other.getPrefixOrderIndex();
+ Slog.w(TAG, "Compare not in the same hierarchy this="
+ + thisParentChain + " thisZ=" + thisZ + " other="
+ + otherParentChain + " otherZ=" + otherZ);
+ return Integer.compare(thisZ, otherZ);
}
// Children are always considered greater than their parents, so if one of the containers
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 294733e..f3e6a18 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -50,5 +50,8 @@
final boolean mInsetsDecoupledConfiguration = Flags.insetsDecoupledConfiguration();
+ final boolean mRespectNonTopVisibleFixedOrientation =
+ Flags.respectNonTopVisibleFixedOrientation();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8033122..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -283,6 +283,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationAdapter;
import android.view.ScrollCaptureResponse;
@@ -334,8 +335,8 @@
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -2569,7 +2570,7 @@
// surface, let the client use that, but don't create new surface at this
// point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+ winAnimator.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2765,15 +2766,15 @@
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController;
+ SurfaceControl surfaceControl;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked();
+ surfaceControl = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (surfaceController != null) {
- surfaceController.getSurfaceControl(outSurfaceControl);
+ if (surfaceControl != null) {
+ winAnimator.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
@@ -6772,11 +6773,11 @@
if (windowState == null) {
return false;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return false;
}
- return surfaceController.clearWindowContentFrameStats();
+ return surfaceControl.clearContentFrameStats();
}
}
@@ -6791,15 +6792,15 @@
if (windowState == null) {
return null;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return null;
}
if (mTempWindowRenderStats == null) {
mTempWindowRenderStats = new WindowContentFrameStats();
}
WindowContentFrameStats stats = mTempWindowRenderStats;
- if (!surfaceController.getWindowContentFrameStats(stats)) {
+ if (!surfaceControl.getContentFrameStats(stats)) {
return null;
}
return stats;
@@ -7440,6 +7441,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPolicy.getApplicationLaunchKeyboardShortcuts(deviceId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 60d3e78..bbd9c0a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1655,6 +1655,22 @@
// Otherwise if other places send wpc.getConfiguration() to client, the configuration may
// be ignored due to the seq is older.
resolvedConfig.seq = newParentConfig.seq;
+
+ if (mConfigActivityRecord != null) {
+ // Let the activity decide whether to apply the size override.
+ return;
+ }
+ final DisplayContent displayContent = mAtm.mWindowManager != null
+ ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
+ : null;
+ applySizeOverrideIfNeeded(
+ displayContent,
+ mInfo,
+ newParentConfig,
+ resolvedConfig,
+ false /* optOutEdgeToEdge */,
+ false /* hasFixedRotationTransform */,
+ false /* hasCompatDisplayInsets */);
}
void dispatchConfiguration(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index de73e6c..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -98,6 +98,7 @@
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -633,6 +634,9 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
+ /**
+ * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
+ */
InputChannel mInputChannel;
/**
@@ -1101,7 +1105,7 @@
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
- mLastReportedActivityWindowInfo = Flags.activityWindowInfoFlag() && mActivityRecord != null
+ mLastReportedActivityWindowInfo = mActivityRecord != null
? new ActivityWindowInfo()
: null;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
@@ -1497,7 +1501,7 @@
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceController);
+ winAnimator.mSurfaceControl);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
@@ -1877,6 +1881,10 @@
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
+ if (removeInputChannelFromWindowstate()) {
+ return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
+ && mInputChannelToken != null && mInputWindowHandle != null;
+ }
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
@@ -2417,7 +2425,7 @@
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
- mWinAnimator.mSurfaceController,
+ mWinAnimator.mSurfaceControl,
Debug.getCallers(5));
final DisplayContent displayContent = getDisplayContent();
@@ -2428,10 +2436,10 @@
mOnBackInvokedCallbackInfo = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mDisplayFrozen=%b callers=%s",
- this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+ this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2608,7 +2616,7 @@
+ isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
&& mActivityRecord != null && mActivityRecord.isVisible()));
if (!isVisibleRequestedOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
@@ -2626,6 +2634,19 @@
}
void openInputChannel(@NonNull InputChannel outInputChannel) {
+ if (mInputChannelToken != null) {
+ throw new IllegalStateException("Window already has an input channel token.");
+ }
+ if (removeInputChannelFromWindowstate()) {
+ String name = getName();
+ InputChannel channel = mWmService.mInputManager.createInputChannel(name);
+ mInputChannelToken = channel.getToken();
+ mInputWindowHandle.setToken(mInputChannelToken);
+ mWmService.mInputToWindowMap.put(mInputChannelToken, this);
+ channel.copyTo(outInputChannel);
+ channel.dispose();
+ return;
+ }
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
@@ -2657,9 +2678,11 @@
mInputChannelToken = null;
}
- if (mInputChannel != null) {
- mInputChannel.dispose();
- mInputChannel = null;
+ if (!removeInputChannelFromWindowstate()) {
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
mInputWindowHandle.setToken(null);
}
@@ -4434,7 +4457,7 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
- if (c.mWinAnimator.mSurfaceController != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
@@ -4891,7 +4914,7 @@
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
@@ -5512,13 +5535,13 @@
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
@@ -5694,7 +5717,7 @@
}
SurfaceControl getClientViewRootSurface() {
- return mWinAnimator.getSurfaceControl();
+ return mWinAnimator.mSurfaceControl;
}
/** Drops a buffer for this window's view-root from a transaction */
@@ -6094,11 +6117,10 @@
}
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
} else {
- if (mWinAnimator.mSurfaceController == null
- || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+ if (mWinAnimator.mSurfaceControl == null) {
return;
}
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+ getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
isSecure);
}
if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -52,6 +57,7 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.Trace;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
- final boolean mIsWallpaper;
private final WallpaperController mWallpaperControllerLocked;
boolean mAnimationIsEntrance;
- WindowSurfaceController mSurfaceController;
+ SurfaceControl mSurfaceControl;
+ private boolean mSurfaceShown;
+ private String mTitle;
float mShownAlpha = 0;
float mAlpha = 0;
@@ -164,7 +171,6 @@
mWin = win;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
- mIsWallpaper = win.mIsWallpaper;
mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
}
@@ -198,14 +204,30 @@
}
void hide(SurfaceControl.Transaction transaction, String reason) {
- if (!mLastHidden) {
- //dump();
- mLastHidden = true;
-
- if (mSurfaceController != null) {
- mSurfaceController.hide(transaction, reason);
- }
+ if (mLastHidden) {
+ return;
}
+ mLastHidden = true;
+ if (mSurfaceControl == null || !mSurfaceShown) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+ setShown(false);
+ transaction.hide(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.getDisplayContent();
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ private void setShown(boolean surfaceShown) {
+ mSurfaceShown = surfaceShown;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+ mWin.onSurfaceShownChanged(surfaceShown);
+ mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
}
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
- mSurfaceController);
+ mSurfaceControl);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
@@ -248,7 +270,7 @@
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
- mSurfaceController);
+ mSurfaceControl);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
}
}
- WindowSurfaceController createSurfaceLocked() {
+ SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
- if (mSurfaceController != null) {
- return mSurfaceController;
+ if (mSurfaceControl != null) {
+ return mSurfaceControl;
}
w.setHasSurface(false);
@@ -312,10 +334,22 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
- flags, this, attrs.type);
+ mTitle = attrs.getTitle().toString();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = mWin.makeSurface()
+ .setParent(mWin.mSurfaceControl)
+ .setName(mTitle)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+ .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+ .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
if (!setScPropertiesInClient()) {
- mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+ setColorSpaceAgnosticLocked(
(attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
@@ -326,7 +360,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
- mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+ mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
flags, this);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "Got surface: " + mSurfaceController
+ Slog.v(TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
}
@@ -353,15 +387,19 @@
mLastHidden = true;
if (DEBUG) Slog.v(TAG, "Created surface " + this);
- return mSurfaceController;
+ return mSurfaceControl;
}
boolean hasSurface() {
- return mSurfaceController != null && mSurfaceController.hasSurface();
+ return mSurfaceControl != null;
+ }
+
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
@@ -370,7 +408,7 @@
try {
if (DEBUG_VISIBILITY) {
logWithStack(TAG, "Window " + this + " destroying surface "
- + mSurfaceController + ", session " + mSession);
+ + mSurfaceControl + ", session " + mSession);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurfaceController + " session " + mSession + ": "
+ + " surface " + mSurfaceControl + " session " + mSession + ": "
+ e.toString());
}
-
- // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
- // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
- // so it can be recreated successfully in mPendingDestroySurface case.
- mWin.setHasSurface(false);
- if (mSurfaceController != null) {
- mSurfaceController.setShown(false);
- }
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
}
void computeShownFrameLocked() {
- if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+ if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
return;
} else if (mWin.isDragResizeChanged()) {
// This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
- mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+ mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
- boolean prepared =
- mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+ t.setAlpha(mSurfaceControl, mShownAlpha);
- if (prepared && mDrawState == HAS_DRAWN) {
+ if (mDrawState == HAS_DRAWN) {
if (mLastHidden) {
- mSurfaceController.showRobustly(t);
+ showRobustly(t);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
}
}
- void setOpaqueLocked(boolean isOpaque) {
- if (mSurfaceController == null) {
+ private void showRobustly(SurfaceControl.Transaction t) {
+ if (mSurfaceShown) {
return;
}
- mSurfaceController.setOpaque(isOpaque);
+
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ setShown(true);
+ t.show(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.mDisplayContent;
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ void setOpaqueLocked(boolean isOpaque) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+ mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnosticLocked(boolean agnostic) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
- mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+ mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
}
void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it. Also skip play enter animation for the window
// below starting window.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
&& !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
@@ -614,8 +661,10 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- if (mSurfaceController != null) {
- mSurfaceController.dumpDebug(proto, SURFACE);
+ if (mSurfaceControl != null) {
+ final long dumpToken = proto.start(SURFACE);
+ proto.write(SHOWN, mSurfaceShown);
+ proto.end(dumpToken);
}
proto.write(DRAW_STATE, mDrawState);
mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
if (mAnimationIsEntrance) {
pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
}
- if (mSurfaceController != null) {
- mSurfaceController.dump(pw, prefix, dumpAll);
+ if (mSurfaceControl != null) {
+ if (dumpAll) {
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+ }
+ pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
}
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
}
boolean getShown() {
- if (mSurfaceController != null) {
- return mSurfaceController.getShown();
- }
- return false;
+ return mSurfaceControl != null && mSurfaceShown;
}
void destroySurface(SurfaceControl.Transaction t) {
- try {
- if (mSurfaceController != null) {
- mSurfaceController.destroy(t);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception thrown when destroying surface " + this
- + " surface " + mSurfaceController + " session " + mSession + ": " + e);
- } finally {
- mWin.setHasSurface(false);
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
+ if (mSurfaceControl == null) {
+ return;
}
- }
-
- SurfaceControl getSurfaceControl() {
- if (!hasSurface()) {
- return null;
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+ "Destroying surface %s called by %s", this, Debug.getCallers(8));
+ if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+ + " by " + Debug.getCallers(8));
}
- return mSurfaceController.mSurfaceControl;
+ t.remove(mSurfaceControl);
+ setShown(false);
+ mSurfaceControl = null;
+ mWin.setHasSurface(false);
+ mDrawState = NO_SURFACE;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
- static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
- final WindowStateAnimator mAnimator;
-
- SurfaceControl mSurfaceControl;
-
- // Should only be set from within setShown().
- private boolean mSurfaceShown = false;
-
- private final String title;
-
- private final WindowManagerService mService;
-
- private final int mWindowType;
- private final Session mWindowSession;
-
-
- WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
- int windowType) {
- mAnimator = animator;
-
- title = name;
-
- mService = animator.mService;
- final WindowState win = animator.mWin;
- mWindowType = windowType;
- mWindowSession = win.mSession;
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = win.makeSurface()
- .setParent(win.getSurfaceControl())
- .setName(name)
- .setFormat(format)
- .setFlags(flags)
- .setMetadata(METADATA_WINDOW_TYPE, windowType)
- .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
- .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController")
- .setBLASTLayer().build();
-
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void hide(SurfaceControl.Transaction transaction, String reason) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
- if (mSurfaceShown) {
- hideSurface(transaction);
- }
- }
-
- private void hideSurface(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
- setShown(false);
- try {
- transaction.hide(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 0 /* request hidden */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
- }
-
- void destroy(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "Destroying surface %s called by %s", this, Debug.getCallers(8));
- try {
- if (mSurfaceControl != null) {
- if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
- && !mAnimator.mWin.mRemoveOnExit) {
- // The wallpaper surface should have the same lifetime as its window.
- Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
- + " by " + Debug.getCallers(8));
- }
- t.remove(mSurfaceControl);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error destroying surface in: " + this, e);
- } finally {
- setShown(false);
- mSurfaceControl = null;
- }
- }
-
- boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
- if (mSurfaceControl == null) {
- return false;
- }
-
- t.setAlpha(mSurfaceControl, alpha);
- return true;
- }
-
- void setOpaque(boolean isOpaque) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
- if (mSurfaceControl == null) {
- return;
- }
-
- mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
- mService.scheduleAnimationLocked();
- }
-
- void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
- if (mSurfaceControl == null) {
- return;
- }
- t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
- }
-
- void showRobustly(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
- + " during relayout");
-
- if (mSurfaceShown) {
- return;
- }
-
- setShown(true);
- t.show(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 1 /* request shown */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- }
-
- boolean clearWindowContentFrameStats() {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.clearContentFrameStats();
- }
-
- boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.getContentFrameStats(outStats);
- }
-
- boolean hasSurface() {
- return mSurfaceControl != null;
- }
-
- void getSurfaceControl(SurfaceControl outSurfaceControl) {
- outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
- }
-
- boolean getShown() {
- return mSurfaceShown;
- }
-
- void setShown(boolean surfaceShown) {
- mSurfaceShown = surfaceShown;
-
- mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
- mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
- if (mWindowSession != null) {
- mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(SHOWN, mSurfaceShown);
- proto.end(token);
- }
-
- public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
- }
- pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
- }
-
- @Override
- public String toString() {
- return mSurfaceControl.toString();
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ba5323e..21f7eca 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -18,89 +18,52 @@
import static android.os.Build.IS_USER;
-import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
-import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
import android.annotation.Nullable;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.util.TraceBuffer;
-import java.io.File;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * A class that allows window manager to dump its state continuously, such that a
* time series of window manager state can be analyzed after the fact.
*/
-class WindowTracing {
-
- /**
- * Maximum buffer size, currently defined as 5 MB
- * Size was experimentally defined to fit between 100 to 150 elements.
- */
- private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
- private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
- private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
- static final String WINSCOPE_EXT = ".winscope";
- private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
- private static final String TAG = "WindowTracing";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+abstract class WindowTracing {
+ protected static final String TAG = "WindowTracing";
+ protected static final String WHERE_START_TRACING = "trace.enable";
+ protected static final String WHERE_ON_FRAME = "onFrame";
private final WindowManagerService mService;
private final Choreographer mChoreographer;
private final WindowManagerGlobalLock mGlobalLock;
- private final Object mEnabledLock = new Object();
- private final File mTraceFile;
- private final TraceBuffer mBuffer;
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
- log("onFrame" /* where */);
+ log(WHERE_ON_FRAME);
- private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
- private boolean mLogOnFrame = false;
- private boolean mEnabled;
- private volatile boolean mEnabledLockFree;
- private boolean mScheduled;
+ private AtomicBoolean mScheduled = new AtomicBoolean(false);
- private final IProtoLog mProtoLog;
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
Choreographer choreographer) {
- File file = new File(TRACE_FILENAME);
- return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM);
+ return new WindowTracingLegacy(service, choreographer);
}
- private WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- int bufferCapacity) {
- this(file, service, choreographer, service.mGlobalLock, bufferCapacity);
- }
-
- WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- WindowManagerGlobalLock globalLock, int bufferCapacity) {
+ protected WindowTracing(WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock) {
mChoreographer = choreographer;
mService = service;
mGlobalLock = globalLock;
- mTraceFile = file;
- mBuffer = new TraceBuffer(bufferCapacity);
- setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
- mProtoLog = ProtoLog.getSingleInstance();
}
void startTrace(@Nullable PrintWriter pw) {
@@ -108,44 +71,29 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
- log("trace.enable");
+ startTraceInternal(pw);
}
- /**
- * Stops the trace and write the current buffer to disk
- * @param pw Print writer
- */
void stopTrace(@Nullable PrintWriter pw) {
if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- mEnabled = mEnabledLockFree = false;
-
- if (mEnabled) {
- logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
- throw new IllegalStateException("tracing enabled while waiting for flush.");
- }
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- }
if (!android.tracing.Flags.perfettoProtologTracing()) {
((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
}
+ stopTraceInternal(pw);
}
/**
- * Stops the trace and write the current buffer to disk then restart, if it's already running.
+ * If legacy tracing is enabled (either WM or ProtoLog):
+ * 1. Stop tracing
+ * 2. Write trace to disk (to be picked by dumpstate)
+ * 3. Restart tracing
+ *
* @param pw Print writer
*/
void saveForBugreport(@Nullable PrintWriter pw) {
@@ -153,143 +101,24 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!mEnabled) {
- return;
- }
- mEnabled = mEnabledLockFree = false;
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).stopProtoLog(pw, true);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).startProtoLog(pw);
- }
+ if (!android.tracing.Flags.perfettoProtologTracing()
+ && ProtoLog.getSingleInstance().isProtoEnabled()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
+ saveForBugreportInternal(pw);
}
- private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
- mLogLevel = logLevel;
-
- switch (logLevel) {
- case WindowTraceLogLevel.ALL: {
- setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
- break;
- }
- case WindowTraceLogLevel.TRIM: {
- setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
- break;
- }
- case WindowTraceLogLevel.CRITICAL: {
- setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
- break;
- }
- }
- }
-
- private void setLogFrequency(boolean onFrame, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log frequency to "
- + ((onFrame) ? "frame" : "transaction"));
- mLogOnFrame = onFrame;
- }
-
- private void setBufferCapacity(int capacity, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
- mBuffer.setCapacity(capacity);
- }
-
- boolean isEnabled() {
- return mEnabledLockFree;
- }
-
- int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArgRequired();
- switch (cmd) {
- case "start":
- startTrace(pw);
- return 0;
- case "stop":
- stopTrace(pw);
- return 0;
- case "save-for-bugreport":
- saveForBugreport(pw);
- return 0;
- case "status":
- logAndPrintln(pw, getStatus());
- return 0;
- case "frame":
- setLogFrequency(true /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "transaction":
- setLogFrequency(false /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "level":
- String logLevelStr = shell.getNextArgRequired().toLowerCase();
- switch (logLevelStr) {
- case "all": {
- setLogLevel(WindowTraceLogLevel.ALL, pw);
- break;
- }
- case "trim": {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- case "critical": {
- setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
- break;
- }
- default: {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- }
- mBuffer.resetBuffer();
- return 0;
- case "size":
- setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
- mBuffer.resetBuffer();
- return 0;
- default:
- pw.println("Unknown command: " + cmd);
- pw.println("Window manager trace options:");
- pw.println(" start: Start logging");
- pw.println(" stop: Stop logging");
- pw.println(" save-for-bugreport: Save logging data to file if it's running.");
- pw.println(" frame: Log trace once per frame");
- pw.println(" transaction: Log each transaction");
- pw.println(" size: Set the maximum log size (in KB)");
- pw.println(" status: Print trace status");
- pw.println(" level [lvl]: Set the log level between");
- pw.println(" lvl may be one of:");
- pw.println(" critical: Only visible windows with reduced information");
- pw.println(" trim: All windows with reduced");
- pw.println(" all: All window and information");
- return -1;
- }
- }
-
- String getStatus() {
- return "Status: "
- + ((isEnabled()) ? "Enabled" : "Disabled")
- + "\n"
- + "Log level: "
- + mLogLevel
- + "\n"
- + mBuffer.getStatus();
- }
+ abstract void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw);
+ abstract void setLogFrequency(boolean onFrame, PrintWriter pw);
+ abstract void setBufferCapacity(int capacity, PrintWriter pw);
+ abstract boolean isEnabled();
+ abstract int onShellCommand(ShellCommand shell);
+ abstract String getStatus();
/**
* If tracing is enabled, log the current state or schedule the next frame to be logged,
- * according to {@link #mLogOnFrame}.
+ * according to the configuration in the derived tracing class.
*
* @param where Logging point descriptor
*/
@@ -298,59 +127,63 @@
return;
}
- if (mLogOnFrame) {
- schedule();
- } else {
+ if (shouldLogOnTransaction()) {
log(where);
}
+
+ if (shouldLogOnFrame()) {
+ schedule();
+ }
}
/**
* Schedule the log to trace the next frame
*/
private void schedule() {
- if (mScheduled) {
+ if (!mScheduled.compareAndSet(false, true)) {
return;
}
- mScheduled = true;
mChoreographer.postFrameCallback(mFrameCallback);
}
/**
- * Write the current frame to the buffer
+ * Write the current frame to proto
*
+ * @param os Proto stream buffer
+ * @param logLevel Log level
* @param where Logging point descriptor
+ * @param elapsedRealtimeNanos Timestamp
*/
- private void log(String where) {
+ protected void dumpToProto(ProtoOutputStream os, @WindowTraceLogLevel int logLevel,
+ String where, long elapsedRealtimeNanos) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
try {
- ProtoOutputStream os = new ProtoOutputStream();
- long tokenOuter = os.start(ENTRY);
- os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(ELAPSED_REALTIME_NANOS, elapsedRealtimeNanos);
os.write(WHERE, where);
- long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
try {
- mService.dumpDebugLocked(os, mLogLevel);
+ mService.dumpDebugLocked(os, logLevel);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- os.end(tokenInner);
- os.end(tokenOuter);
- mBuffer.add(os);
- mScheduled = false;
+ os.end(token);
} catch (Exception e) {
Log.wtf(TAG, "Exception while tracing state", e);
} finally {
+ boolean isOnFrameLogEvent = where == WHERE_ON_FRAME;
+ if (isOnFrameLogEvent) {
+ mScheduled.set(false);
+ }
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+ protected void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Log.i(TAG, msg);
if (pw != null) {
pw.println(msg);
@@ -358,24 +191,10 @@
}
}
- /**
- * Writes the trace buffer to disk. This method has no internal synchronization and should be
- * externally synchronized
- */
- private void writeTraceToFileLocked() {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
- ProtoOutputStream proto = new ProtoOutputStream();
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- long timeOffsetNs =
- TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- - SystemClock.elapsedRealtimeNanos();
- proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
- mBuffer.writeTraceToFile(mTraceFile, proto);
- } catch (IOException e) {
- Log.e(TAG, "Unable to write buffer to file", e);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
+ protected abstract void startTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void stopTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void saveForBugreportInternal(@Nullable PrintWriter pw);
+ protected abstract void log(String where);
+ protected abstract boolean shouldLogOnFrame();
+ protected abstract boolean shouldLogOnTransaction();
}
diff --git a/services/core/java/com/android/server/wm/WindowTracingLegacy.java b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
new file mode 100644
index 0000000..7a36707
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import com.android.internal.util.TraceBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+class WindowTracingLegacy extends WindowTracing {
+
+ /**
+ * Maximum buffer size, currently defined as 5 MB
+ * Size was experimentally defined to fit between 100 to 150 elements.
+ */
+ private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
+ private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
+ private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mEnabledLock = new Object();
+ private final File mTraceFile;
+ private final TraceBuffer mBuffer;
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ protected @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
+ protected boolean mLogOnFrame = false;
+
+ WindowTracingLegacy(WindowManagerService service, Choreographer choreographer) {
+ this(new File(TRACE_FILENAME), service, choreographer,
+ service.mGlobalLock, BUFFER_CAPACITY_TRIM);
+ }
+
+ WindowTracingLegacy(File traceFile, WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock, int bufferSize) {
+ super(service, choreographer, globalLock);
+ mTraceFile = traceFile;
+ mBuffer = new TraceBuffer(bufferSize);
+ }
+
+ @Override
+ void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
+ mLogLevel = logLevel;
+
+ switch (logLevel) {
+ case WindowTraceLogLevel.ALL: {
+ setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
+ break;
+ }
+ case WindowTraceLogLevel.TRIM: {
+ setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
+ break;
+ }
+ case WindowTraceLogLevel.CRITICAL: {
+ setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
+ break;
+ }
+ }
+ }
+
+ @Override
+ void setLogFrequency(boolean onFrame, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log frequency to "
+ + ((onFrame) ? "frame" : "transaction"));
+ mLogOnFrame = onFrame;
+ }
+
+ @Override
+ void setBufferCapacity(int capacity, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
+ mBuffer.setCapacity(capacity);
+ }
+
+ @Override
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ @Override
+ int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArgRequired();
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ case "save-for-bugreport":
+ saveForBugreport(pw);
+ return 0;
+ case "status":
+ logAndPrintln(pw, getStatus());
+ return 0;
+ case "frame":
+ setLogFrequency(true /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "transaction":
+ setLogFrequency(false /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "level":
+ String logLevelStr = shell.getNextArgRequired().toLowerCase();
+ switch (logLevelStr) {
+ case "all": {
+ setLogLevel(WindowTraceLogLevel.ALL, pw);
+ break;
+ }
+ case "trim": {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ case "critical": {
+ setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
+ break;
+ }
+ default: {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ }
+ mBuffer.resetBuffer();
+ return 0;
+ case "size":
+ setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ pw.println("Window manager trace options:");
+ pw.println(" start: Start logging");
+ pw.println(" stop: Stop logging");
+ pw.println(" save-for-bugreport: Save logging data to file if it's running.");
+ pw.println(" frame: Log trace once per frame");
+ pw.println(" transaction: Log each transaction");
+ pw.println(" size: Set the maximum log size (in KB)");
+ pw.println(" status: Print trace status");
+ pw.println(" level [lvl]: Set the log level between");
+ pw.println(" lvl may be one of:");
+ pw.println(" critical: Only visible windows with reduced information");
+ pw.println(" trim: All windows with reduced");
+ pw.println(" all: All window and information");
+ return -1;
+ }
+ }
+
+ @Override
+ String getStatus() {
+ return "Status: "
+ + ((isEnabled()) ? "Enabled" : "Disabled")
+ + "\n"
+ + "Log level: "
+ + mLogLevel
+ + "\n"
+ + mBuffer.getStatus();
+ }
+
+ @Override
+ protected void startTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ log(WHERE_START_TRACING);
+ }
+
+ @Override
+ protected void stopTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ @Override
+ protected void saveForBugreportInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ if (!mEnabled) {
+ return;
+ }
+ mEnabled = mEnabledLockFree = false;
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ @Override
+ protected void log(String where) {
+ try {
+ ProtoOutputStream os = new ProtoOutputStream();
+ long token = os.start(ENTRY);
+ dumpToProto(os, mLogLevel, where, SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ mBuffer.add(os);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ }
+ }
+
+ @Override
+ protected boolean shouldLogOnFrame() {
+ return mLogOnFrame;
+ }
+
+ @Override
+ protected boolean shouldLogOnTransaction() {
+ return !mLogOnFrame;
+ }
+
+ private void writeTraceToFileLocked() {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
+ mBuffer.writeTraceToFile(mTraceFile, proto);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java b/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java
new file mode 100644
index 0000000..5e49345
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/DimenPxIntSupplier.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.annotation.DimenRes;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import java.util.function.IntSupplier;
+
+/**
+ * Cached version of IntSupplier customised to evaluate new dimen in pixels
+ * when density changes.
+ * @hide
+ */
+public class DimenPxIntSupplier implements IntSupplier {
+
+ @NonNull
+ private final Context mContext;
+
+ private final int mResourceId;
+
+ private float mLastDensity = Float.MIN_VALUE;
+ private int mValue = 0;
+
+ public DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
+ mContext = context;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public int getAsInt() {
+ final float newDensity = mContext.getResources().getDisplayMetrics().density;
+ if (newDensity != mLastDensity) {
+ mLastDensity = newDensity;
+ mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
+ }
+ return mValue;
+ }
+}
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index a32b0f1..9ec5ae5 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -125,6 +125,9 @@
case DeviceType::ROTARY_ENCODER:
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+ if (vd_flags::high_resolution_scroll()) {
+ ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
+ }
break;
default:
ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1f0c827..5719810 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -314,6 +314,7 @@
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+ void notifyConfigurationChanged(nsecs_t when) override;
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -331,7 +332,6 @@
void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
uint32_t policyFlags) override;
- void notifyConfigurationChanged(nsecs_t when) override;
// ANR-related callbacks -- start
void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid,
@@ -382,6 +382,7 @@
PointerControllerInterface::ControllerType type) override;
void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
const FloatPoint& position) override;
+ void notifyMouseCursorFadedOnTyping() override;
/* --- InputFilterPolicyInterface implementation --- */
void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -788,6 +789,10 @@
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::notifyMouseCursorFadedOnTyping() {
+ mInputManager->getReader().notifyMouseCursorFadedOnTyping();
+}
+
void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
uint32_t lockedModifierState) {
JNIEnv* env = jniEnv();
@@ -1847,10 +1852,6 @@
const std::shared_ptr<InputChannel>& inputChannel,
void* data) {
NativeInputManager* im = static_cast<NativeInputManager*>(data);
-
- ALOGW("Input channel object '%s' was disposed without first being removed with "
- "the input manager!",
- inputChannel->getName().c_str());
im->removeInputChannel(inputChannel->getConnectionToken());
}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index eeb8b9b..4231149 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -179,6 +179,19 @@
<xs:element name="supportsVrr" type="xs:boolean" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Table that translates doze brightness sensor values to brightness values in
+ the float scale [0, 1]; -1 means the current brightness should be kept.
+ The following formula should be used for conversion between nits and the float
+ scale: float = (nits - minNits) / (maxNits - minNits). minNits and maxNits are
+ defined in screenBrightnessMap. -->
+ <xs:element type="float-array" name="dozeBrightnessSensorValueToBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- The default screen brightness in the scale [0, 1] to use while the device is
+ dozing. -->
+ <xs:element type="nonNegativeDecimal" name="defaultDozeBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -315,6 +328,42 @@
<xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
+
+ <!-- The minimum HDR layer % at which hdr boost will be applied with transition point cap.
+ Should be lower or equal to minimumHdrPercentOfScreenForHbm. -->
+ <xs:element name="minimumHdrPercentOfScreenForNbm" type="nonNegativeDecimal"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- The minimum HDR layer size at which hdr boost will be applied. -->
+ <xs:element name="minimumHdrPercentOfScreenForHbm" type="nonNegativeDecimal"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- If hdr boost is allowed in low power mode. -->
+ <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- sdrNits, hdrRatio This LUT specifies how to boost HDR brightness at given SDR brightness (nits). -->
+ <!-- sdr brightness to hdr boost ratio map
+ Format: first = sdrNits, second = hdrRatio. E.g. :
+ <sdrHdrRatioMap>
+ <point>
+ <first>2.0</first> // sdrNits
+ <second>4.0</second> // hdrRatio
+ </point>
+ ....
+ </sdrHdrRatioMap>
+ -->
+ <xs:element type="nonNegativeFloatToFloatMap" name="sdrHdrRatioMap" minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+
+
</xs:complexType>
<!-- Maps to PowerManager.THERMAL_STATUS_* values. -->
@@ -823,6 +872,12 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="float-array">
+ <xs:sequence>
+ <xs:element name="item" type="nonNegativeDecimal" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="usiVersion">
<xs:element name="majorVersion" type="xs:nonNegativeInteger"
minOccurs="1" maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 757b23a..cec2787 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -125,9 +125,11 @@
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method public com.android.server.display.config.AutoBrightness getAutoBrightness();
+ method public final java.math.BigDecimal getDefaultDozeBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method public final com.android.server.display.config.FloatArray getDozeBrightnessSensorValueToBrightness();
method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -163,9 +165,11 @@
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
+ method public final void setDefaultDozeBrightness(java.math.BigDecimal);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setDozeBrightnessSensorValueToBrightness(com.android.server.display.config.FloatArray);
method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
@@ -214,6 +218,11 @@
method public void setTransitionPoint(java.math.BigDecimal);
}
+ public class FloatArray {
+ ctor public FloatArray();
+ method public java.util.List<java.math.BigDecimal> getItem();
+ }
+
public class HbmTiming {
ctor public HbmTiming();
method @NonNull public final java.math.BigInteger getTimeMaxSecs_all();
@@ -226,16 +235,24 @@
public class HdrBrightnessConfig {
ctor public HdrBrightnessConfig();
+ method @NonNull public final boolean getAllowInLowPowerMode();
method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+ method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreenForHbm();
+ method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreenForNbm();
method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
+ method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getSdrHdrRatioMap();
+ method public final void setAllowInLowPowerMode(@NonNull boolean);
method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setMinimumHdrPercentOfScreenForHbm(@Nullable java.math.BigDecimal);
+ method public final void setMinimumHdrPercentOfScreenForNbm(@Nullable java.math.BigDecimal);
method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
+ method public final void setSdrHdrRatioMap(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
}
public class HighBrightnessMode {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e5a1ebf..db4b171 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -381,6 +381,9 @@
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+ private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS =
+ "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle";
+
/*
* Implementation class names and jar locations for services in
@@ -1196,15 +1199,18 @@
mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class);
t.traceEnd();
- // Initialize RescueParty.
- RescueParty.registerHealthObserver(mSystemContext);
- if (!Flags.recoverabilityDetection()) {
- // Now that we have the bare essentials of the OS up and running, take
- // note that we just booted, which might send out a rescue party if
- // we're stuck in a runtime restart loop.
- PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ if (!Flags.refactorCrashrecovery()) {
+ // Initialize RescueParty.
+ RescueParty.registerHealthObserver(mSystemContext);
+ if (!Flags.recoverabilityDetection()) {
+ // Now that we have the bare essentials of the OS up and running, take
+ // note that we just booted, which might send out a rescue party if
+ // we're stuck in a runtime restart loop.
+ PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ }
}
+
// Manages LEDs and display backlight so we need it to bring up the display.
t.traceBegin("StartLightsService");
mSystemServiceManager.startService(LightsService.class);
@@ -2931,12 +2937,18 @@
mPackageManagerService.systemReady();
t.traceEnd();
- if (Flags.recoverabilityDetection()) {
- // Now that we have the essential services needed for mitigations, register the boot
- // with package watchdog.
- // Note that we just booted, which might send out a rescue party if we're stuck in a
- // runtime restart loop.
- PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ if (Flags.refactorCrashrecovery()) {
+ t.traceBegin("StartCrashRecoveryModule");
+ mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS);
+ t.traceEnd();
+ } else {
+ if (Flags.recoverabilityDetection()) {
+ // Now that we have the essential services needed for mitigations, register the boot
+ // with package watchdog.
+ // Note that we just booted, which might send out a rescue party if we're stuck in a
+ // runtime restart loop.
+ PackageWatchdog.getInstance(mSystemContext).noteBoot();
+ }
}
t.traceBegin("MakeDisplayManagerServiceReady");
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 8ff1a7d..114fe32 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,9 +4,4 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
- <hal format="aidl">
- <name>android.frameworks.vibrator</name>
- <version>1</version>
- <fqname>IVibratorControlService/default</fqname>
- </hal>
</manifest>
diff --git a/services/manifest_services_android.frameworks.vibrator.xml b/services/manifest_services_android.frameworks.vibrator.xml
new file mode 100644
index 0000000..c287643
--- /dev/null
+++ b/services/manifest_services_android.frameworks.vibrator.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.frameworks.vibrator</name>
+ <version>1</version>
+ <fqname>IVibratorControlService/default</fqname>
+ </hal>
+</manifest>
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3d40f64..927146d 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -21,6 +21,7 @@
":services.net-sources",
],
static_libs: [
+ "modules-utils-build_system",
"netd-client",
"networkstack-client",
"net-utils-services-common",
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index 6b20ef1..996daf5 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -47,9 +47,8 @@
/** Write to actual file and reserve file. */
@Throws(IOException::class)
inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
- val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
- reserveFile.delete()
writeInlined(block)
+ val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
try {
FileInputStream(baseFile).use { inputStream ->
FileOutputStream(reserveFile).use { outputStream ->
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2029b71..ce68b86 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -21,6 +21,7 @@
import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -33,18 +34,20 @@
import android.graphics.Insets;
import android.os.RemoteException;
import android.provider.Settings;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
import android.util.Log;
import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;
@@ -66,6 +69,10 @@
private static final String TAG = "SimpleIMSTest";
private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
private static final String EDIT_TEXT_DESC = "Input box";
+ private static final String INPUT_METHOD_NAV_BACK_ID =
+ "android:id/input_method_nav_back";
+ private static final String INPUT_METHOD_NAV_IME_SWITCHER_ID =
+ "android:id/input_method_nav_ime_switcher";
private static final long TIMEOUT_IN_SECONDS = 3;
private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
"settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
@@ -99,6 +106,12 @@
InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
assertThat(mInputMethodService).isNotNull();
+ // The activity gets focus.
+ assertThat(mActivity.hasWindowFocus()).isTrue();
+ assertThat(mInputMethodService.getCurrentInputEditorInfo()).isNotNull();
+ assertThat(mInputMethodService.getCurrentInputEditorInfo().packageName)
+ .isEqualTo(mTargetPackageName);
+
// The editor won't bring up keyboard by default.
assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
@@ -691,6 +704,151 @@
assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse();
}
+ /**
+ * Verifies that clicking on the IME navigation bar back button hides the IME.
+ */
+ @Test
+ public void testBackButtonClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_BACK_ID);
+ backButtonUiObject.click();
+ mInstrumentation.waitForIdleSync();
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
+
+ /**
+ * Verifies that long clicking on the IME navigation bar back button hides the IME.
+ */
+ @Test
+ public void testBackButtonLongClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var backButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_BACK_ID);
+ backButtonUiObject.longClick();
+ mInstrumentation.waitForIdleSync();
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
+
+ /**
+ * Verifies that clicking on the IME switch button shows the Input Method Switcher Menu.
+ */
+ @Test
+ public void testImeSwitchButtonClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var imm = mContext.getSystemService(InputMethodManager.class);
+
+ final var imeSwitchButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_IME_SWITCHER_ID);
+ imeSwitchButtonUiObject.click();
+ mInstrumentation.waitForIdleSync();
+
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(imm))
+ .isTrue();
+
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ // Hide the Picker menu before finishing.
+ mUiDevice.pressBack();
+ }
+
+ /**
+ * Verifies that long clicking on the IME switch button shows the Input Method Switcher Menu.
+ */
+ @Test
+ public void testImeSwitchButtonLongClick() throws Exception {
+ boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService()
+ .hasNavigationBar(mInputMethodService.getDisplayId());
+ assumeTrue("Must have a navigation bar", hasNavigationBar);
+ assumeTrue("Must be in gesture navigation mode", isGestureNavEnabled());
+
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ verifyInputViewStatusOnMainSync(
+ () -> {
+ // Ensure the IME navigation bar and the IME switch button are drawn.
+ mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged(
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR
+ | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN
+ );
+ assertThat(mActivity.showImeWithWindowInsetsController()).isTrue();
+ },
+ true /* expected */,
+ true /* inputViewStarted */);
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var imm = mContext.getSystemService(InputMethodManager.class);
+
+ final var imeSwitchButtonUiObject = getUiObjectById(INPUT_METHOD_NAV_IME_SWITCHER_ID);
+ imeSwitchButtonUiObject.longClick();
+ mInstrumentation.waitForIdleSync();
+
+ assertWithMessage("Input Method Switcher Menu is shown")
+ .that(isInputMethodPickerShown(imm))
+ .isTrue();
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+ // Hide the Picker menu before finishing.
+ mUiDevice.pressBack();
+ }
+
private void verifyInputViewStatus(
Runnable runnable, boolean expected, boolean inputViewStarted)
throws InterruptedException {
@@ -838,6 +996,32 @@
return SystemUtil.runShellCommandOrThrow(cmd);
}
+ /**
+ * Checks if the Input Method Switcher Menu is shown. This runs by adopting the Shell's
+ * permission to ensure we have TEST_INPUT_METHOD permission.
+ */
+ private static boolean isInputMethodPickerShown(@NonNull InputMethodManager imm) {
+ return SystemUtil.runWithShellPermissionIdentity(imm::isInputMethodPickerShown);
+ }
+
+ @NonNull
+ private UiObject2 getUiObjectById(@NonNull String id) {
+ final var uiObject = mUiDevice.wait(
+ Until.findObject(By.res(id)),
+ TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+ assertThat(uiObject).isNotNull();
+ return uiObject;
+ }
+
+ /**
+ * Returns {@code true} if the navigation mode is gesture nav, and {@code false} otherwise.
+ */
+ private boolean isGestureNavEnabled() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+ }
+
private void clickOnEditorText() {
// Find the editText and click it.
UiObject2 editTextUiObject =
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 7c8f1190..c2a069d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.notNull;
@@ -38,6 +39,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.hardware.input.IInputManager;
@@ -66,7 +68,6 @@
import com.android.internal.view.IInputMethodManager;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -157,8 +158,9 @@
mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(InputMethodUtils.class)
.mockStatic(ServiceManager.class)
- .mockStatic(SystemServerInitThreadPool.class)
+ .spyStatic(AdditionalSubtypeMapRepository.class)
.startMocking();
mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -227,23 +229,32 @@
.thenReturn(TEST_IME_TARGET_INFO);
when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder);
- // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
- // which is ok to be mocked out for now.
- doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+ // This changes the real IME component state. Not appropriate to do in tests.
+ doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+ any(PackageManager.class), anyList()));
+
+ // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
+ doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
mServiceThread =
new ServiceThread(
"immstest1",
Process.THREAD_PRIORITY_FOREGROUND,
true /* allowIo */);
+ mServiceThread.start();
mIoThread =
new ServiceThread(
"immstest2",
Process.THREAD_PRIORITY_FOREGROUND,
true /* allowIo */);
+ mIoThread.start();
+
+ final var ioHandler = spy(Handler.createAsync(mIoThread.getLooper()));
+ doReturn(true).when(ioHandler).post(any());
+
mInputMethodManagerService = new InputMethodManagerService(mContext,
InputMethodManagerService.shouldEnableConcurrentMultiUserMode(mContext),
- mServiceThread, mIoThread,
+ mServiceThread.getLooper(), ioHandler,
unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
@@ -255,16 +266,9 @@
// Public local InputMethodManagerService.
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
lifecycle.onStart();
- try {
- // After this boot phase, services can broadcast Intents.
- lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
- } catch (SecurityException e) {
- // Security exception to permission denial is expected in test, mocking out to ensure
- // InputMethodManagerService as system ready state.
- if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
- throw e;
- }
- }
+
+ // After this boot phase, services can broadcast Intents.
+ lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
// Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index e81cf9d..dc03732 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -16,15 +16,26 @@
package com.android.server.inputmethod;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_RECENT;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_STATIC;
+import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.SwitchMode;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -35,6 +46,7 @@
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -51,6 +63,9 @@
private static final String SYSTEM_LOCALE = "en_US";
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@NonNull
private static InputMethodSubtype createTestSubtype(@NonNull String locale) {
return new InputMethodSubtypeBuilder()
@@ -170,7 +185,7 @@
subtype = createTestSubtype(currentItem.mSubtypeName.toString());
}
final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
- currentItem.mImi, subtype);
+ currentItem.mImi, subtype, MODE_STATIC, true /* forward */);
assertEquals(nextItem, nextIme);
}
@@ -185,15 +200,16 @@
}
}
- private void onUserAction(@NonNull ControllerImpl controller,
+ private boolean onUserAction(@NonNull ControllerImpl controller,
@NonNull ImeSubtypeListItem subtypeListItem) {
InputMethodSubtype subtype = null;
if (subtypeListItem.mSubtypeName != null) {
subtype = createTestSubtype(subtypeListItem.mSubtypeName.toString());
}
- controller.onUserActionLocked(subtypeListItem.mImi, subtype);
+ return controller.onUserActionLocked(subtypeListItem.mImi, subtype);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@Test
public void testControllerImpl() {
final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
@@ -213,7 +229,7 @@
final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
- null /* currentInstance */, enabledItems);
+ null /* currentInstance */, enabledItems, new ArrayList<>());
// switching-aware loop
assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -257,6 +273,7 @@
disabledSubtypeUnawareIme, null);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@Test
public void testControllerImplWithUserAction() {
final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
@@ -270,7 +287,7 @@
final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(7);
final ControllerImpl controller = ControllerImpl.createFrom(
- null /* currentInstance */, enabledItems);
+ null /* currentInstance */, enabledItems, new ArrayList<>());
// === switching-aware loop ===
assertRotationOrder(controller, false /* onlyCurrentIme */,
@@ -320,7 +337,7 @@
// Rotation order should be preserved when created with the same subtype list.
final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
final ControllerImpl newController = ControllerImpl.createFrom(controller,
- sameEnabledItems);
+ sameEnabledItems, new ArrayList<>());
assertRotationOrder(newController, false /* onlyCurrentIme */,
subtypeAwareIme, latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
assertRotationOrder(newController, false /* onlyCurrentIme */,
@@ -332,7 +349,7 @@
latinIme_en_us, latinIme_fr, subtypeAwareIme, switchingUnawareLatinIme_en_uk,
switchUnawareJapaneseIme_ja_jp, subtypeUnawareIme);
final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
- differentEnabledItems);
+ differentEnabledItems, new ArrayList<>());
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
latinIme_en_us, latinIme_fr, subtypeAwareIme);
assertRotationOrder(anotherController, false /* onlyCurrentIme */,
@@ -370,6 +387,7 @@
assertFalse(item_en_us_allcaps.mIsSystemLanguage);
}
+ @RequiresFlagsDisabled(Flags.FLAG_IME_SWITCHER_REVAMP)
@SuppressWarnings("SelfComparison")
@Test
public void testImeSubtypeListComparator() {
@@ -471,4 +489,739 @@
assertNotEquals(ime2, ime1);
}
}
+
+ /** Verifies the static mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeStatic() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_STATIC;
+
+ // Static mode matches the given items order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // Set french IME as most recent.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ // Static mode is not influenced by recency updates on non-hardware item.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ // Static mode is not influenced by recency updates on hardware item.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+ }
+
+ /** Verifies the recency mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeRecent() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_RECENT;
+
+ // Recency order is initialized to static order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ // The order of non-hardware items is updated.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items remains unchanged for an action on a non-hardware item.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertFalse("Recency not updated again for same IME", onUserAction(controller, french));
+
+ // The order of non-hardware items remains unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items remains unchanged.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var recencyHardwareItems =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatinIme =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimpleIme = List.of(hardwareSimple);
+
+ // The order of non-hardware items is unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of hardware items is updated.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+ }
+
+ /** Verifies the auto mode. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testModeAuto() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_AUTO;
+
+ // Auto mode resolves to static order initially.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ // Auto mode resolves to recency order for the first forward after user action, and to
+ // static order for the backwards direction.
+ assertNextOrder(controller, false /* forHardware */, mode, true /* forward */,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+ assertNextOrder(controller, false /* forHardware */, mode, false /* forward */,
+ items.reversed(), List.of(latinIme.reversed(), simpleIme.reversed()));
+
+ // Auto mode resolves to recency order for the first forward after user action,
+ // but the recency was not updated for hardware items, so it's equivalent to static order.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // Change IME, reset user action having happened.
+ controller.onInputMethodSubtypeChanged();
+
+ // Auto mode resolves to static order as there was no user action since changing IMEs.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME again.
+ assertFalse("Recency not updated again for same IME", onUserAction(controller, french));
+
+ // Auto mode still resolves to static order, as a user action on the currently most
+ // recent IME has no effect.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on hardware french IME.
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var recencyHardware =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatin =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimple = List.of(hardwareSimple);
+
+ // Auto mode resolves to recency order for the first forward direction after a user action
+ // on a hardware IME, and to static order for the backwards direction.
+ assertNextOrder(controller, false /* forHardware */, mode, true /* forward */,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+ assertNextOrder(controller, false /* forHardware */, mode, false /* forward */,
+ items.reversed(), List.of(latinIme.reversed(), simpleIme.reversed()));
+
+ assertNextOrder(controller, true /* forHardware */, mode, true /* forward */,
+ recencyHardware, List.of(recencyHardwareLatin, recencyHardwareSimple));
+
+ assertNextOrder(controller, true /* forHardware */, mode, false /* forward */,
+ hardwareItems.reversed(),
+ List.of(hardwareLatinIme.reversed(), hardwareSimpleIme.reversed()));
+ }
+
+ /**
+ * Verifies that the recency order is preserved only when updating with an equal list of items.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testUpdateList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(items, "SimpleIme", "SimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var english = items.get(0);
+ final var french = items.get(1);
+ final var italian = items.get(2);
+ final var simple = items.get(3);
+
+ final var latinIme = List.of(english, french, italian);
+ final var simpleIme = List.of(simple);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareLatinIme", "HardwareLatinIme",
+ List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */);
+ addTestImeSubtypeListItems(hardwareItems, "HardwareSimpleIme", "HardwareSimpleIme",
+ null, true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareEnglish = hardwareItems.get(0);
+ final var hardwareFrench = hardwareItems.get(1);
+ final var hardwareItalian = hardwareItems.get(2);
+ final var hardwareSimple = hardwareItems.get(3);
+
+ final var hardwareLatinIme = List.of(hardwareEnglish, hardwareFrench, hardwareItalian);
+ final var hardwareSimpleIme = List.of(hardwareSimple);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ final int mode = MODE_RECENT;
+
+ // Recency order is initialized to static order.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ items, List.of(latinIme, simpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ // User action on french IME.
+ assertTrue("Recency updated for french IME", onUserAction(controller, french));
+
+ final var equalItems = new ArrayList<>(items);
+ final var otherItems = new ArrayList<>(items);
+ otherItems.remove(simple);
+
+ final var equalController = ControllerImpl.createFrom(controller, equalItems,
+ hardwareItems);
+ final var otherController = ControllerImpl.createFrom(controller, otherItems,
+ hardwareItems);
+
+ final var recencyItems = List.of(french, english, italian, simple);
+ final var recencyLatinIme = List.of(french, english, italian);
+ final var recencySimpleIme = List.of(simple);
+
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of equal non-hardware items is unchanged.
+ assertNextOrder(equalController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ // The order of other hardware items is reset.
+ assertNextOrder(otherController, false /* forHardware */, mode,
+ latinIme, List.of(latinIme));
+
+ // The order of hardware remains unchanged.
+ assertNextOrder(controller, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertNextOrder(equalController, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertNextOrder(otherController, true /* forHardware */, mode,
+ hardwareItems, List.of(hardwareLatinIme, hardwareSimpleIme));
+
+ assertTrue("Recency updated for french hardware IME",
+ onUserAction(controller, hardwareFrench));
+
+ final var equalHardwareItems = new ArrayList<>(hardwareItems);
+ final var otherHardwareItems = new ArrayList<>(hardwareItems);
+ otherHardwareItems.remove(hardwareSimple);
+
+ final var equalHardwareController = ControllerImpl.createFrom(controller, items,
+ equalHardwareItems);
+ final var otherHardwareController = ControllerImpl.createFrom(controller, items,
+ otherHardwareItems);
+
+ final var recencyHardwareItems =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian, hardwareSimple);
+ final var recencyHardwareLatinIme =
+ List.of(hardwareFrench, hardwareEnglish, hardwareItalian);
+ final var recencyHardwareSimpleIme = List.of(hardwareSimple);
+
+ // The order of non-hardware items remains unchanged.
+ assertNextOrder(controller, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(equalHardwareController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(otherHardwareController, false /* forHardware */, mode,
+ recencyItems, List.of(recencyLatinIme, recencySimpleIme));
+
+ assertNextOrder(controller, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+
+ // The order of equal hardware items is unchanged.
+ assertNextOrder(equalHardwareController, true /* forHardware */, mode,
+ recencyHardwareItems, List.of(recencyHardwareLatinIme, recencyHardwareSimpleIme));
+
+ // The order of other hardware items is reset.
+ assertNextOrder(otherHardwareController, true /* forHardware */, mode,
+ hardwareLatinIme, List.of(hardwareLatinIme));
+ }
+
+ /** Verifies that switch aware and switch unaware IMEs are combined together. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testSwitchAwareAndUnawareCombined() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "switchAware", "switchAware",
+ null, true /* supportsSwitchingToNextInputMethod*/);
+ addTestImeSubtypeListItems(items, "switchUnaware", "switchUnaware",
+ null, false /* supportsSwitchingToNextInputMethod*/);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchAware", "hardwareSwitchAware",
+ null, true /* supportsSwitchingToNextInputMethod*/);
+ addTestImeSubtypeListItems(hardwareItems, "hardwareSwitchUnaware", "hardwareSwitchUnaware",
+ null, false /* supportsSwitchingToNextInputMethod*/);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
+ assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */,
+ mode, true /* forward */, items);
+ assertNextOrder(controller, false /* forHardware */, false /* onlyCurrentIme */,
+ mode, false /* forward */, items.reversed());
+
+ assertNextOrder(controller, true /* forHardware */, false /* onlyCurrentIme */,
+ mode, true /* forward */, hardwareItems);
+ assertNextOrder(controller, true /* forHardware */, false /* onlyCurrentIme */,
+ mode, false /* forward */, hardwareItems.reversed());
+ }
+ }
+
+ /** Verifies that an empty controller can't take any actions. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testEmptyList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(),
+ List.of());
+
+ assertNoAction(controller, false /* forHardware */, items);
+ assertNoAction(controller, true /* forHardware */, hardwareItems);
+ }
+
+ /** Verifies that a controller with a single item can't take any actions. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testSingleItemList() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */,
+ List.of(items.get(0)), List.of(hardwareItems.get(0)));
+
+ assertNoAction(controller, false /* forHardware */, items);
+ assertNoAction(controller, true /* forHardware */, hardwareItems);
+ }
+
+ /** Verifies that a controller can't take any actions for unknown items. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testUnknownItems() {
+ final var items = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(items, "LatinIme", "LatinIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ final var unknownItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var hardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+ final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>();
+ addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme",
+ List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */);
+
+ final var controller = ControllerImpl.createFrom(null /* currentInstance */, items,
+ hardwareItems);
+
+ assertNoAction(controller, false /* forHardware */, unknownItems);
+ assertNoAction(controller, true /* forHardware */, unknownHardwareItems);
+ }
+
+ /** Verifies that the IME name does influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareImeName() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var imeX = createTestItem(component, "ImeX", "A", "en_US", 0);
+ final var imeY = createTestItem(component, "ImeY", "A", "en_US", 0);
+
+ assertTrue("Smaller IME name should be smaller.", imeX.compareTo(imeY) < 0);
+ assertTrue("Larger IME name should be larger.", imeY.compareTo(imeX) > 0);
+ }
+
+ /** Verifies that the IME ID does influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareImeId() {
+ final var component1 = new ComponentName("com.example.ime1", "Ime");
+ final var component2 = new ComponentName("com.example.ime2", "Ime");
+ final var ime1 = createTestItem(component1, "Ime", "A", "en_US", 0);
+ final var ime2 = createTestItem(component2, "Ime", "A", "en_US", 0);
+
+ assertTrue("Smaller IME ID should be smaller.", ime1.compareTo(ime2) < 0);
+ assertTrue("Larger IME ID should be larger.", ime2.compareTo(ime1) > 0);
+ }
+
+ /** Verifies that comparison on self returns an equal order. */
+ @SuppressWarnings("SelfComparison")
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSelf() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var item = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertEquals("Item should have the same order to itself.", 0, item.compareTo(item));
+ }
+
+ /** Verifies that comparison on an equivalent item returns an equal order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareEquivalent() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var item = createTestItem(component, "Ime", "A", "en_US", 0);
+ final var equivalent = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertEquals("Equivalent items should have the same order.", 0, item.compareTo(equivalent));
+ }
+
+ /**
+ * Verifies that the system locale and system language do not the influence comparison order.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSystemLocaleSystemLanguage() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var japanese = createTestItem(component, "Ime", "A", "ja_JP", 0);
+ final var systemLanguage = createTestItem(component, "Ime", "A", "en_GB", 0);
+ final var systemLocale = createTestItem(component, "Ime", "A", "en_US", 0);
+
+ assertFalse(japanese.mIsSystemLanguage);
+ assertFalse(japanese.mIsSystemLocale);
+ assertTrue(systemLanguage.mIsSystemLanguage);
+ assertFalse(systemLanguage.mIsSystemLocale);
+ assertTrue(systemLocale.mIsSystemLanguage);
+ assertTrue(systemLocale.mIsSystemLocale);
+
+ assertEquals("System language shouldn't influence comparison over non-system language.",
+ 0, japanese.compareTo(systemLanguage));
+ assertEquals("System locale shouldn't influence comparison over non-system locale.",
+ 0, japanese.compareTo(systemLocale));
+ assertEquals("System locale shouldn't influence comparison over system language.",
+ 0, systemLanguage.compareTo(systemLocale));
+ }
+
+ /** Verifies that the subtype name does not influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSubtypeName() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var subtypeA = createTestItem(component, "Ime", "A", "en_US", 0);
+ final var subtypeB = createTestItem(component, "Ime", "B", "en_US", 0);
+
+ assertEquals("Subtype name shouldn't influence comparison.",
+ 0, subtypeA.compareTo(subtypeB));
+ }
+
+ /** Verifies that the subtype index does not influence the comparison order. */
+ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
+ @Test
+ public void testCompareSubtypeIndex() {
+ final var component = new ComponentName("com.example.ime", "Ime");
+ final var subtype0 = createTestItem(component, "Ime1", "A", "en_US", 0);
+ final var subtype1 = createTestItem(component, "Ime1", "A", "en_US", 1);
+
+ assertEquals("Subtype index shouldn't influence comparison.",
+ 0, subtype0.compareTo(subtype1));
+ }
+
+ /**
+ * Verifies that the controller's next item order matches the given one, and cycles back at
+ * the end, both across all IMEs, and also per each IME. If a single item is given, verifies
+ * that no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param allItems the list of items across all IMEs.
+ * @param perImeItems the list of lists of items per IME.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
+ @SwitchMode int mode, boolean forward, @NonNull List<ImeSubtypeListItem> allItems,
+ @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ forward, allItems);
+
+ for (var imeItems : perImeItems) {
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ forward, imeItems);
+ }
+ }
+
+ /**
+ * Verifies that the controller's next item order matches the given one, and cycles back at
+ * the end, both across all IMEs, and also per each IME. This checks the forward direction
+ * with the given items, and the backwards order with the items reversed. If a single item is
+ * given, verifies that no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param mode the switching mode.
+ * @param allItems the list of items across all IMEs.
+ * @param perImeItems the list of lists of items per IME.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller, boolean forHardware,
+ @SwitchMode int mode, @NonNull List<ImeSubtypeListItem> allItems,
+ @NonNull List<List<ImeSubtypeListItem>> perImeItems) {
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ true /* forward */, allItems);
+ assertNextOrder(controller, forHardware, false /* onlyCurrentIme */, mode,
+ false /* forward */, allItems.reversed());
+
+ for (var imeItems : perImeItems) {
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ true /* forward */, imeItems);
+ assertNextOrder(controller, forHardware, true /* onlyCurrentIme */, mode,
+ false /* forward */, imeItems.reversed());
+ }
+ }
+
+ /**
+ * Verifies that the controller's next item order (starting from the first one in {@code items}
+ * matches the given on, and cycles back at the end. If a single item is given, verifies that
+ * no next item is returned.
+ *
+ * @param controller the controller to use for finding the next items.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param items the list of items to verify, in the expected order.
+ */
+ private static void assertNextOrder(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull List<ImeSubtypeListItem> items) {
+ final int numItems = items.size();
+ if (numItems == 0) {
+ return;
+ } else if (numItems == 1) {
+ // Single item controllers should never return a next item.
+ assertNextItem(controller, forHardware, onlyCurrentIme, mode, forward, items.get(0),
+ null /* expectedNext*/);
+ return;
+ }
+
+ var item = items.get(0);
+
+ final var expectedNextItems = new ArrayList<>(items);
+ // Add first item in the last position of expected order, to ensure the order is cyclic.
+ expectedNextItems.add(item);
+
+ final var nextItems = new ArrayList<>();
+ // Add first item in the first position of actual order, to ensure the order is cyclic.
+ nextItems.add(item);
+
+ // Compute the nextItems starting from the first given item, and compare the order.
+ for (int i = 0; i < numItems; i++) {
+ item = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward, item);
+ assertNotNull("Next item shouldn't be null.", item);
+ nextItems.add(item);
+ }
+
+ assertEquals("Rotation order doesn't match.", expectedNextItems, nextItems);
+ }
+
+ /**
+ * Verifies that the controller gets the expected next value from the given item.
+ *
+ * @param controller the controller to sue for finding the next value.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param item the item to find the next value from.
+ * @param expectedNext the expected next value.
+ */
+ private static void assertNextItem(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull ImeSubtypeListItem item, @Nullable ImeSubtypeListItem expectedNext) {
+ final var nextItem = getNextItem(controller, forHardware, onlyCurrentIme, mode, forward,
+ item);
+ assertEquals("Next item doesn't match.", expectedNext, nextItem);
+ }
+
+ /**
+ * Gets the next value from the given item.
+ *
+ * @param controller the controller to use for finding the next value.
+ * @param forHardware whether to find the next hardware item, or software item.
+ * @param onlyCurrentIme whether to consider only subtypes of the current input method.
+ * @param mode the switching mode.
+ * @param forward whether to search forwards or backwards in the list.
+ * @param item the item to find the next value from.
+ * @return the next item found, otherwise {@code null}.
+ */
+ @Nullable
+ private static ImeSubtypeListItem getNextItem(@NonNull ControllerImpl controller,
+ boolean forHardware, boolean onlyCurrentIme, @SwitchMode int mode, boolean forward,
+ @NonNull ImeSubtypeListItem item) {
+ final var subtype = item.mSubtypeName != null
+ ? createTestSubtype(item.mSubtypeName.toString()) : null;
+ return forHardware
+ ? controller.getNextInputMethodForHardware(
+ onlyCurrentIme, item.mImi, subtype, mode, forward)
+ : controller.getNextInputMethod(
+ onlyCurrentIme, item.mImi, subtype, mode, forward);
+ }
+
+ /**
+ * Verifies that no next items can be found, and the recency cannot be updated for the
+ * given items.
+ *
+ * @param controller the controller to verify the items on.
+ * @param forHardware whether to try finding the next hardware item, or software item.
+ * @param items the list of items to verify.
+ */
+ private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware,
+ @NonNull List<ImeSubtypeListItem> items) {
+ for (var item : items) {
+ for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) {
+ assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
+ false /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode,
+ true /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
+ false /* forward */, item, null /* expectedNext */);
+ assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode,
+ true /* forward */, item, null /* expectedNext */);
+ }
+
+ assertFalse("User action shouldn't have updated the recency.",
+ onUserAction(controller, item));
+ }
+ }
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index e2f3eec..81fb1a0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,23 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.pm.UserInfo;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
import android.platform.test.ravenwood.RavenwoodRule;
-import com.android.server.pm.UserManagerInternal;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -52,13 +41,8 @@
.setProvideMainThread(true).build();
@Mock
- private UserManagerInternal mMockUserManagerInternal;
-
- @Mock
private InputMethodManagerService mMockInputMethodManagerService;
- private Handler mHandler;
-
private IntFunction<InputMethodBindingController> mBindingControllerFactory;
@Before
@@ -66,7 +50,6 @@
MockitoAnnotations.initMocks(this);
SecureSettingsWrapper.startTestMode();
- mHandler = new Handler(Looper.getMainLooper());
mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
@Override
@@ -81,54 +64,20 @@
SecureSettingsWrapper.endTestMode();
}
- @Test
- public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
- // Create UserDataRepository and capture the user lifecycle listener
- final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal, bindingControllerFactorySpy);
-
- verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
- final var listener = captor.getValue();
-
- // Assert that UserDataRepository is empty and then call onUserCreated
- assertThat(collectUserData(repository)).isEmpty();
- final var userInfo = new UserInfo();
- userInfo.id = ANY_USER_ID;
- listener.onUserCreated(userInfo, /* unused token */ new Object());
- waitForIdle();
-
- // Assert UserDataRepository contains the expected UserData
- final var allUserData = collectUserData(repository);
- assertThat(allUserData).hasSize(1);
- assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
-
- // Assert UserDataRepository called the InputMethodBindingController creator function.
- verify(bindingControllerFactorySpy).apply(ANY_USER_ID);
- assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
- }
-
+ // TODO(b/352615651): Move this to end-to-end test.
@Test
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
- // Create UserDataRepository and capture the user lifecycle listener
- final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal,
+ // Create UserDataRepository
+ final var repository = new UserDataRepository(
userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
- verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
- final var listener = captor.getValue();
-
// Add one UserData ...
- final var userInfo = new UserInfo();
- userInfo.id = ANY_USER_ID;
- listener.onUserCreated(userInfo, /* unused token */ new Object());
- waitForIdle();
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
+
// ... and then call onUserRemoved
assertThat(collectUserData(repository)).hasSize(1);
- listener.onUserRemoved(userInfo);
- waitForIdle();
+ repository.remove(ANY_USER_ID);
// Assert UserDataRepository is now empty
assertThat(collectUserData(repository)).isEmpty();
@@ -136,13 +85,10 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal, mBindingControllerFactory);
+ final var repository = new UserDataRepository(mBindingControllerFactory);
- synchronized (ImfLock.class) {
- final var userData = repository.getOrCreate(ANY_USER_ID);
- assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
- }
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
@@ -154,15 +100,8 @@
private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
final var collected = new ArrayList<UserDataRepository.UserData>();
- synchronized (ImfLock.class) {
- repository.forAllUserData(userData -> collected.add(userData));
- }
+ repository.forAllUserData(userData -> collected.add(userData));
return collected;
}
- private void waitForIdle() {
- final var done = new ConditionVariable();
- mHandler.post(done::open);
- done.block();
- }
}
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 b706a65..67212b6 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
@@ -53,13 +53,14 @@
@Override
public void onStartInput(EditorInfo info, boolean restarting) {
- Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
+ Log.i(TAG, "onStartInput() editor=" + dumpEditorInfo(info) + ", restarting=" + restarting);
super.onStartInput(info, restarting);
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
- Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
+ Log.i(TAG, "onStartInputView() editor=" + dumpEditorInfo(info)
+ + ", restarting=" + restarting);
super.onStartInputView(info, restarting);
mInputViewStarted = true;
if (mCountDownLatchForTesting != null) {
@@ -99,4 +100,14 @@
mCountDownLatchForTesting.countDown();
}
}
+
+ private String dumpEditorInfo(EditorInfo info) {
+ var sb = new StringBuilder();
+ sb.append("EditorInfo{packageName=").append(info.packageName);
+ sb.append(" fieldId=").append(info.fieldId);
+ sb.append(" hintText=").append(info.hintText);
+ sb.append(" privateImeOptions=").append(info.privateImeOptions);
+ sb.append("}");
+ return sb.toString();
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 89b4aea..7138306 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1027,6 +1027,25 @@
}
@Test
+ public void testWriteReadDebuggable() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ packageSetting.setDebuggable(true);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_1).isDebuggable(), is(true));
+ }
+
+ @Test
public void testWriteReadArchiveState() {
Settings settings = makeSettings();
PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 9a25b1a..d450683 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -54,6 +54,7 @@
import com.android.internal.R;
import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.RefreshRateData;
@@ -436,7 +437,7 @@
public void testHighBrightnessModeDataFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
- DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ HighBrightnessModeData hbmData =
mDisplayDeviceConfig.getHighBrightnessModeData();
assertNotNull(hbmData);
assertEquals(BRIGHTNESS[1], hbmData.transitionPoint, ZERO_DELTA);
@@ -671,14 +672,14 @@
HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData();
assertNotNull(data);
- assertEquals(2, data.mMaxBrightnessLimits.size());
- assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
- assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
- assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
- assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
+ assertEquals(2, data.maxBrightnessLimits.size());
+ assertEquals(13000, data.brightnessDecreaseDebounceMillis);
+ assertEquals(0.1f, data.screenBrightnessRampDecrease, SMALL_DELTA);
+ assertEquals(1000, data.brightnessIncreaseDebounceMillis);
+ assertEquals(0.11f, data.screenBrightnessRampIncrease, SMALL_DELTA);
- assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
- assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
+ assertEquals(0.3f, data.maxBrightnessLimits.get(500f), SMALL_DELTA);
+ assertEquals(0.6f, data.maxBrightnessLimits.get(1200f), SMALL_DELTA);
}
private void verifyConfigValuesFromConfigResource() {
@@ -964,6 +965,51 @@
assertThat(supportedModeData.vsyncRate).isEqualTo(240);
}
+ @Test
+ public void testDozeBrightness_Ddc() throws IOException {
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertArrayEquals(new float[]{ -1, 0.1f, 0.2f, 0.3f, 0.4f },
+ mDisplayDeviceConfig.getDozeBrightnessSensorValueToBrightness(), SMALL_DELTA);
+ assertEquals(0.25f, mDisplayDeviceConfig.getDefaultDozeBrightness(), SMALL_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlFloat() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(0.31f);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(0.31f, mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlInt() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(brightnessIntToFloat(90),
+ mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1707,6 +1753,16 @@
+ "</point>"
+ "</luxThresholds>"
+ "</idleScreenRefreshRateTimeout>"
+ + "<dozeBrightnessSensorValueToBrightness>\n"
+ + "<item>-1</item>\n"
+ + "<item>0.1</item>\n"
+ + "<item>0.2</item>\n"
+ + "<item>0.3</item>\n"
+ + "<item>0.4</item>\n"
+ + "</dozeBrightnessSensorValueToBrightness>\n"
+ + "<defaultDozeBrightness>"
+ + "0.25"
+ + "</defaultDozeBrightness>\n"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 2018e1a..624c897 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -83,6 +83,7 @@
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -116,6 +117,7 @@
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
private static final float DOZE_SCALE_FACTOR = 0.34f;
+ private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f;
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
@@ -2050,9 +2052,6 @@
@Test
public void testDefaultDozeBrightness() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2068,15 +2067,25 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
+
+ // The display device changes and the default doze brightness changes
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mHolder.config, /* isEnabled= */ true);
+ when(mHolder.config.getDefaultDozeBrightness()).thenReturn(DEFAULT_DOZE_BRIGHTNESS / 2);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS / 2),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
}
@Test
public void testDefaultDozeBrightness_ShouldNotBeUsedIfAutoBrightnessAllowedInDoze() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2092,7 +2101,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator, never()).animateTo(eq(brightness),
+ verify(mHolder.animator, never()).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
}
@@ -2150,6 +2159,8 @@
new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getDefaultDozeBrightness())
+ .thenReturn(DEFAULT_DOZE_BRIGHTNESS);
when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
@@ -2425,7 +2436,7 @@
@Override
HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
- float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+ float brightnessMax, HighBrightnessModeData hbmData,
HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
Context context) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 8e01a11..cde87b9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -23,7 +23,6 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static org.junit.Assert.assertEquals;
@@ -56,8 +55,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.HighBrightnessModeController.Injector;
+import com.android.server.display.config.HighBrightnessModeData;
import com.android.server.testutils.OffsettableClock;
import org.junit.Before;
@@ -77,6 +76,7 @@
private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
+ private static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
private static final float DEFAULT_MIN = 0.01f;
private static final float DEFAULT_MAX = 0.80f;
@@ -103,7 +103,8 @@
private static final HighBrightnessModeData DEFAULT_HBM_DATA =
new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
- ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+ ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+ null, null, true);
@Before
public void setUp() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
index 7e7ccf7..7132bc1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
+import com.android.server.display.config.HighBrightnessModeData;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -39,7 +41,7 @@
private DisplayDeviceConfig mDdcMock;
@Mock
- private DisplayDeviceConfig.HighBrightnessModeData mHbmDataMock;
+ private HighBrightnessModeData mHbmDataMock;
private HighBrightnessModeMetadataMapper mHighBrightnessModeMetadataMapper;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 6d138c5..1729ad5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,7 +16,7 @@
package com.android.server.display;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
import static android.view.Display.FLAG_REAR;
@@ -62,9 +62,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
@@ -103,7 +105,9 @@
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -111,9 +115,12 @@
private static int sUniqueTestDisplayId = 0;
private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500;
private static final int FOLD_SETTLE_DELAY = 1000;
- private static final int DEVICE_STATE_CLOSED = 0;
- private static final int DEVICE_STATE_HALF_OPEN = 1;
- private static final int DEVICE_STATE_OPEN = 2;
+ private static final DeviceState DEVICE_STATE_CLOSED = createDeviceState(0, "Zero",
+ Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP), Collections.emptySet());
+ private static final DeviceState DEVICE_STATE_HALF_OPEN = createDeviceState(1, "One",
+ Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
+ private static final DeviceState DEVICE_STATE_OPEN = createDeviceState(2, "Two",
+ Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
private static final int FLAG_GO_TO_SLEEP_ON_FOLD = 0;
private static final int FLAG_GO_TO_SLEEP_FLAG_SOFT_SLEEP = 2;
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
@@ -703,8 +710,7 @@
/* isInteractive= */true,
/* isBootCompleted= */true));
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
- INVALID_DEVICE_STATE_IDENTIFIER,
- /* isInteractive= */true,
+ INVALID_DEVICE_STATE /* currentState */, /* isInteractive= */true,
/* isBootCompleted= */true));
}
@@ -932,7 +938,7 @@
// We can only have one default display
assertEquals(DEFAULT_DISPLAY, id(display1));
- mLogicalDisplayMapper.setDeviceStateLocked(0);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
advanceTime(1000);
// The new state is not applied until the boot is completed
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
@@ -953,7 +959,7 @@
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
- mLogicalDisplayMapper.setDeviceStateLocked(1);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_HALF_OPEN);
advanceTime(1000);
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -966,7 +972,7 @@
mLogicalDisplayMapper.getDisplayLocked(device2)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
- mLogicalDisplayMapper.setDeviceStateLocked(2);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN);
advanceTime(1000);
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -1043,7 +1049,7 @@
// 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
// 4) Dispatch handler events.
mLogicalDisplayMapper.onBootCompleted();
- mLogicalDisplayMapper.setDeviceStateLocked(0);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -1073,7 +1079,7 @@
/* includeDisabled= */ false));
// Now do it again to go back to state 1
- mLogicalDisplayMapper.setDeviceStateLocked(1);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_HALF_OPEN);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -1127,7 +1133,7 @@
// We can only have one default display
assertEquals(DEFAULT_DISPLAY, id(display1));
- mLogicalDisplayMapper.setDeviceStateLocked(0);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
advanceTime(1000);
mLogicalDisplayMapper.onBootCompleted();
advanceTime(1000);
@@ -1180,13 +1186,15 @@
Layout layout = new Layout();
createDefaultDisplay(layout, outer);
createNonDefaultDisplay(layout, inner, /* enabled= */ false, /* group= */ null);
- when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_CLOSED)).thenReturn(layout);
+ when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_CLOSED.getIdentifier())).thenReturn(
+ layout);
layout = new Layout();
createNonDefaultDisplay(layout, outer, /* enabled= */ false, /* group= */ null);
createDefaultDisplay(layout, inner);
- when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_HALF_OPEN)).thenReturn(layout);
- when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_OPEN)).thenReturn(layout);
+ when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_HALF_OPEN.getIdentifier())).thenReturn(
+ layout);
+ when(mDeviceStateToLayoutMapSpy.get(DEVICE_STATE_OPEN.getIdentifier())).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
add(outer);
@@ -1317,6 +1325,15 @@
assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
}
+ private static DeviceState createDeviceState(int identifier, @NonNull String name,
+ @NonNull Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties,
+ @NonNull Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties) {
+ DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder(
+ identifier, name).setSystemProperties(systemProperties).setPhysicalProperties(
+ physicalProperties).build();
+ return new DeviceState(deviceStateConfiguration);
+ }
+
private final static class FoldableDisplayDevices {
final TestDisplayDevice mOuter;
final TestDisplayDevice mInner;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c785ea6..7212856 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -34,6 +34,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.config.DisplayDeviceConfigTestUtilsKt;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -54,13 +55,14 @@
private static final float FLOAT_TOLERANCE = 0.0001f;
private static final long SEND_TIME_TOLERANCE = 100;
- private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
- Map.of(500f, 0.6f),
- /* brightnessIncreaseDebounceMillis= */ 1000,
- /* screenBrightnessRampIncrease= */ 0.02f,
- /* brightnessDecreaseDebounceMillis= */ 3000,
- /* screenBrightnessRampDecrease= */0.04f
- );
+ private static final HdrBrightnessData TEST_HDR_DATA = DisplayDeviceConfigTestUtilsKt
+ .createHdrBrightnessData(
+ Map.of(500f, 0.6f),
+ /* brightnessIncreaseDebounceMillis= */ 1000,
+ /* screenBrightnessRampIncrease= */ 0.02f,
+ /* brightnessDecreaseDebounceMillis= */ 3000,
+ /* screenBrightnessRampDecrease= */0.04f
+ );
private static final int WIDTH = 600;
private static final int HEIGHT = 800;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
new file mode 100644
index 0000000..3b3d6f7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config
+
+import android.util.Spline
+import android.util.Xml
+import com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.OutputStreamWriter
+import org.xmlpull.v1.XmlSerializer
+
+fun createRefreshRateData(
+ defaultRefreshRate: Int = 60,
+ defaultPeakRefreshRate: Int = 60,
+ defaultRefreshRateInHbmHdr: Int = 60,
+ defaultRefreshRateInHbmSunlight: Int = 60,
+ lowPowerSupportedModes: List<SupportedModeData> = emptyList(),
+ lowLightBlockingZoneSupportedModes: List<SupportedModeData> = emptyList()
+): RefreshRateData {
+ return RefreshRateData(
+ defaultRefreshRate, defaultPeakRefreshRate,
+ defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+ lowPowerSupportedModes, lowLightBlockingZoneSupportedModes
+ )
+}
+
+@JvmOverloads
+fun createHdrBrightnessData(
+ maxBrightnessLimits: Map<Float, Float> = mapOf(Pair(500f, 0.6f)),
+ brightnessIncreaseDebounceMillis: Long = 1000,
+ screenBrightnessRampIncrease: Float = 0.02f,
+ brightnessDecreaseDebounceMillis: Long = 3000,
+ screenBrightnessRampDecrease: Float = 0.04f,
+ minimumHdrPercentOfScreenForNbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+ minimumHdrPercentOfScreenForHbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+ allowInLowPowerMode: Boolean = false,
+ sdrToHdrRatioSpline: Spline? = null
+): HdrBrightnessData {
+ return HdrBrightnessData(
+ maxBrightnessLimits,
+ brightnessIncreaseDebounceMillis,
+ screenBrightnessRampIncrease,
+ brightnessDecreaseDebounceMillis,
+ screenBrightnessRampDecrease,
+ minimumHdrPercentOfScreenForNbm,
+ minimumHdrPercentOfScreenForHbm,
+ allowInLowPowerMode,
+ sdrToHdrRatioSpline
+ )
+}
+
+fun XmlSerializer.highBrightnessMode(
+ enabled: String = "true",
+ transitionPoint: String = "0.67",
+ minimumLux: String = "2500",
+ timeWindowSecs: String = "200",
+ timeMaxSecs: String = "30",
+ timeMinSecs: String = "3",
+ refreshRateRange: Pair<String, String>? = null,
+ allowInLowPowerMode: String? = null,
+ minimumHdrPercentOfScreen: String? = null,
+ sdrHdrRatioMap: List<Pair<String, String>>? = null,
+) {
+ element("highBrightnessMode") {
+ attribute("", "enabled", enabled)
+ element("transitionPoint", transitionPoint)
+ element("minimumLux", minimumLux)
+ element("timing") {
+ element("timeWindowSecs", timeWindowSecs)
+ element("timeMaxSecs", timeMaxSecs)
+ element("timeMinSecs", timeMinSecs)
+ }
+ pair("refreshRate", "minimum", "maximum", refreshRateRange)
+ element("allowInLowPowerMode", allowInLowPowerMode)
+ element("minimumHdrPercentOfScreen", minimumHdrPercentOfScreen)
+ map("sdrHdrRatioMap", "point", "sdrNits", "hdrRatio", sdrHdrRatioMap)
+ }
+}
+
+fun XmlSerializer.hdrBrightnessConfig(
+ brightnessMap: List<Pair<String, String>> = listOf(Pair("500", "0.6")),
+ brightnessIncreaseDebounceMillis: String = "1000",
+ screenBrightnessRampIncrease: String = "0.02",
+ brightnessDecreaseDebounceMillis: String = "3000",
+ screenBrightnessRampDecrease: String = "0.04",
+ minimumHdrPercentOfScreenForNbm: String? = null,
+ minimumHdrPercentOfScreenForHbm: String? = null,
+ allowInLowPowerMode: String? = null,
+ sdrHdrRatioMap: List<Pair<String, String>>? = null,
+) {
+ element("hdrBrightnessConfig") {
+ map("brightnessMap", "point", "first", "second", brightnessMap)
+ element("brightnessIncreaseDebounceMillis", brightnessIncreaseDebounceMillis)
+ element("screenBrightnessRampIncrease", screenBrightnessRampIncrease)
+ element("brightnessDecreaseDebounceMillis", brightnessDecreaseDebounceMillis)
+ element("screenBrightnessRampDecrease", screenBrightnessRampDecrease)
+ element("minimumHdrPercentOfScreenForNbm", minimumHdrPercentOfScreenForNbm)
+ element("minimumHdrPercentOfScreenForHbm", minimumHdrPercentOfScreenForHbm)
+ element("allowInLowPowerMode", allowInLowPowerMode)
+ map("sdrHdrRatioMap", "point", "first", "second", sdrHdrRatioMap)
+ }
+}
+
+fun createDisplayConfiguration(content: XmlSerializer.() -> Unit = { }): DisplayConfiguration {
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ val xmlSerializer = Xml.newSerializer()
+ OutputStreamWriter(byteArrayOutputStream).use { writer ->
+ xmlSerializer.setOutput(writer)
+ xmlSerializer.startDocument("UTF-8", true)
+ xmlSerializer.startTag("", "displayConfiguration")
+ xmlSerializer.content()
+ xmlSerializer.endTag("", "displayConfiguration")
+ xmlSerializer.endDocument()
+ }
+ return XmlParser.read(ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
+}
+
+private fun XmlSerializer.map(
+ rootName: String,
+ nodeName: String,
+ keyName: String,
+ valueName: String,
+ map: List<Pair<String, String>>?
+) {
+ map?.let { m ->
+ element(rootName) {
+ m.forEach { e -> pair(nodeName, keyName, valueName, e) }
+ }
+ }
+}
+
+private fun XmlSerializer.pair(
+ nodeName: String,
+ keyName: String,
+ valueName: String,
+ pair: Pair<String, String>?
+) {
+ pair?.let {
+ element(nodeName) {
+ element(keyName, pair.first)
+ element(valueName, pair.second)
+ }
+ }
+}
+
+private fun XmlSerializer.element(name: String, content: String?) {
+ if (content != null) {
+ startTag("", name)
+ text(content)
+ endTag("", name)
+ }
+}
+
+private fun XmlSerializer.element(name: String, content: XmlSerializer.() -> Unit) {
+ startTag("", name)
+ content()
+ endTag("", name)
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
new file mode 100644
index 0000000..19c6924
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config
+
+import android.util.Spline.createSpline
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayBrightnessState
+import com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class HdrBrightnessDataTest {
+
+ @Test
+ fun `test HdrBrightnessData default configuration`() {
+ val displayConfiguration = createDisplayConfiguration {
+ hdrBrightnessConfig(
+ brightnessDecreaseDebounceMillis = "3000",
+ screenBrightnessRampDecrease = "0.05",
+ brightnessIncreaseDebounceMillis = "2000",
+ screenBrightnessRampIncrease = "0.03",
+ brightnessMap = listOf(Pair("500", "0.6"), Pair("600", "0.7")),
+ minimumHdrPercentOfScreenForNbm = null,
+ minimumHdrPercentOfScreenForHbm = null,
+ allowInLowPowerMode = null,
+ sdrHdrRatioMap = null,
+ )
+ }
+
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ assertThat(hdrBrightnessData).isNotNull()
+
+ assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(3000)
+ assertThat(hdrBrightnessData.screenBrightnessRampDecrease).isEqualTo(0.05f)
+ assertThat(hdrBrightnessData.brightnessIncreaseDebounceMillis).isEqualTo(2000)
+ assertThat(hdrBrightnessData.screenBrightnessRampIncrease).isEqualTo(0.03f)
+
+ assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(2)
+ assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(500f, 0.6f)
+ assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(600f, 0.7f)
+
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(
+ HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+ )
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(
+ HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+ )
+ assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+ assertThat(hdrBrightnessData.sdrToHdrRatioSpline).isNull()
+ }
+
+ @Test
+ fun `test HdrBrightnessData fallback configuration`() {
+ val displayConfiguration = createDisplayConfiguration {
+ hdrBrightnessConfig(
+ minimumHdrPercentOfScreenForNbm = null,
+ minimumHdrPercentOfScreenForHbm = null,
+ allowInLowPowerMode = null,
+ sdrHdrRatioMap = null,
+ )
+ highBrightnessMode(
+ minimumHdrPercentOfScreen = "0.2",
+ sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+ )
+ }
+
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ assertThat(hdrBrightnessData).isNotNull()
+
+ assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
+ assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+
+ val expectedSpline = createSpline(floatArrayOf(2.0f, 5.0f), floatArrayOf(4.0f, 8.0f))
+ assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+ .isEqualTo(expectedSpline.toString())
+ }
+
+ @Test
+ fun `test HdrBrightnessData fallback configuration no hdrBrightnessConfig`() {
+ val displayConfiguration = createDisplayConfiguration {
+ highBrightnessMode(
+ minimumHdrPercentOfScreen = "0.2",
+ sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+ )
+ }
+
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ assertThat(hdrBrightnessData).isNotNull()
+
+ assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(0)
+ assertThat(hdrBrightnessData.screenBrightnessRampDecrease)
+ .isEqualTo(DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET)
+ assertThat(hdrBrightnessData.brightnessIncreaseDebounceMillis).isEqualTo(0)
+ assertThat(hdrBrightnessData.screenBrightnessRampIncrease)
+ .isEqualTo(DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET)
+
+ assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(0)
+
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
+ assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+
+ val expectedSpline = createSpline(floatArrayOf(2.0f, 5.0f), floatArrayOf(4.0f, 8.0f))
+ assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+ .isEqualTo(expectedSpline.toString())
+ }
+
+ @Test
+ fun `test HdrBrightnessData configuration no configuration`() {
+ val displayConfiguration = createDisplayConfiguration()
+
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ assertThat(hdrBrightnessData).isNull()
+ }
+
+ @Test
+ fun `test HdrBrightnessData real configuration`() {
+ val displayConfiguration = createDisplayConfiguration {
+ hdrBrightnessConfig(
+ minimumHdrPercentOfScreenForNbm = "0.3",
+ minimumHdrPercentOfScreenForHbm = "0.6",
+ allowInLowPowerMode = "true",
+ sdrHdrRatioMap = listOf(Pair("3.0", "5.0"), Pair("6.0", "8.0"))
+ )
+ highBrightnessMode(
+ minimumHdrPercentOfScreen = "0.2",
+ sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+ )
+ }
+
+ val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+ assertThat(hdrBrightnessData).isNotNull()
+
+ assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
+ assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.6f)
+ assertThat(hdrBrightnessData.allowInLowPowerMode).isTrue()
+
+ val expectedSpline = createSpline(floatArrayOf(3.0f, 6.0f), floatArrayOf(5.0f, 8.0f))
+ assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+ .isEqualTo(expectedSpline.toString())
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 95702aa..3c77ec9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -25,6 +25,7 @@
import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.config.RefreshRateData
import com.android.server.display.config.SupportedModeData
+import com.android.server.display.config.createRefreshRateData
import com.android.server.display.feature.DisplayManagerFlags
import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
import com.android.server.testutils.TestHandler
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index e431c8c..4fc574a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -29,6 +29,7 @@
import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.config.RefreshRateData
import com.android.server.display.config.SupportedModeData
+import com.android.server.display.config.createRefreshRateData
import com.android.server.display.feature.DisplayManagerFlags
import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 5b07166..0b34fce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -16,9 +16,6 @@
package com.android.server.display.mode
-import com.android.server.display.config.RefreshRateData
-import com.android.server.display.config.SupportedModeData
-
internal fun createVotesSummary(
isDisplayResolutionRangeVotingEnabled: Boolean = true,
supportedModesVoteEnabled: Boolean = true,
@@ -29,15 +26,3 @@
loggingEnabled, supportsFrameRateOverride)
}
-fun createRefreshRateData(
- defaultRefreshRate: Int = 60,
- defaultPeakRefreshRate: Int = 60,
- defaultRefreshRateInHbmHdr: Int = 60,
- defaultRefreshRateInHbmSunlight: Int = 60,
- lowPowerSupportedModes: List<SupportedModeData> = emptyList(),
- lowLightBlockingZoneSupportedModes: List<SupportedModeData> = emptyList()
-): RefreshRateData {
- return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
- defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
- lowPowerSupportedModes, lowLightBlockingZoneSupportedModes)
-}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 4149e44..5b2c0c6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
"mockingservicestests-utils-mockito",
"mockito-target-extended-minus-junit4",
"platform-compat-test-rules",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"PlatformProperties",
"service-blobstore",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 419bcb8..e610a32 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -527,7 +527,7 @@
final ProcessRecord appRec = new ProcessRecord(mAms, info, info.processName, uid);
final ProcessStatsService tracker = mAms.mProcessStats;
- final IApplicationThread appThread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
doReturn(mock(IBinder.class)).when(appThread).asBinder();
appRec.makeActive(appThread, tracker);
mAms.mProcessList.addProcessNameLocked(appRec);
@@ -701,7 +701,8 @@
final var wpc = fifoProc.getWindowProcessController();
spyOn(wpc);
doReturn(true).when(wpc).useFifoUiScheduling();
- fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+ fifoProc.makeActive(new ApplicationThreadDeferred(fifoProc.getThread()),
+ mAms.mProcessStats);
assertTrue(fifoProc.useFifoUiScheduling());
assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
new file mode 100644
index 0000000..8f8c1ac
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+
+/**
+ * Tests to verify that the ApplicationThreadDeferred properly defers binder calls to paused
+ * processes.
+ */
+@SmallTest
+public class ApplicationThreadDeferredTest {
+ private static final String TAG = "ApplicationThreadDeferredTest";
+
+ private void callDeferredApis(IApplicationThread thread) throws Exception {
+ thread.clearDnsCache();
+ thread.updateHttpProxy();
+ thread.updateTimeZone();
+ thread.scheduleLowMemory();
+ }
+
+ // Verify that the special APIs have been called count times.
+ private void verifyDeferredApis(IApplicationThread thread, int count) throws Exception {
+ verify(thread, times(count)).clearDnsCache();
+ verify(thread, times(count)).updateHttpProxy();
+ verify(thread, times(count)).updateTimeZone();
+ verify(thread, times(count)).scheduleLowMemory();
+ }
+
+ // Test the baseline behavior of IApplicationThread. If this test fails, all other tests are
+ // suspect.
+ @Test
+ public void testBaseline() throws Exception {
+ IApplicationThread thread = mock(IApplicationThread.class);
+ callDeferredApis(thread);
+ verifyDeferredApis(thread, 1);
+ }
+
+ // Test the baseline behavior of IApplicationThreadDeferred. If this test fails, all other
+ // tests are suspect.
+ @Test
+ public void testBaselineDeferred() throws Exception {
+ IApplicationThread thread = mock(ApplicationThreadDeferred.class);
+ callDeferredApis(thread);
+ verifyDeferredApis(thread, 1);
+ }
+
+ // Verify that a deferred thread behaves like a regular thread when it is not paused.
+ @Test
+ public void testDeferredUnpaused() throws Exception {
+ IApplicationThread base = mock(IApplicationThread.class);
+ ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+ callDeferredApis(thread);
+ verifyDeferredApis(base, 1);
+ }
+
+ // Verify that a paused deferred thread thread does not deliver any calls to its parent. Then
+ // unpause the thread and verify that the collapsed calls are forwarded.
+ @Test
+ public void testDeferredPaused() throws Exception {
+ IApplicationThread base = mock(IApplicationThread.class);
+ ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+ thread.onProcessPaused();
+ callDeferredApis(thread);
+ callDeferredApis(thread);
+ verifyDeferredApis(base, 0);
+ thread.onProcessUnpaused();
+ verifyDeferredApis(base, 1);
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 80f7a06..93066d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -189,7 +189,7 @@
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
throws Exception {
- final IApplicationThread thread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 13ba1e5..3aaf2e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -325,13 +325,13 @@
ProcessRecord.updateProcessRecordNodes(r);
mActiveProcesses.add(r);
- final IApplicationThread thread;
+ final ApplicationThreadDeferred thread;
if (dead) {
- thread = mock(IApplicationThread.class, (invocation) -> {
+ thread = mock(ApplicationThreadDeferred.class, (invocation) -> {
throw new DeadObjectException();
});
} else {
- thread = mock(IApplicationThread.class);
+ thread = mock(ApplicationThreadDeferred.class);
}
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 240ddf5..0796f41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -760,7 +760,7 @@
ProcessStatsService processStatsService = new ProcessStatsService(
mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
"procstats"));
- app.makeActive(mock(IApplicationThread.class), processStatsService);
+ app.makeActive(mock(ApplicationThreadDeferred.class), processStatsService);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 6366f24..1dbd532 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3209,7 +3209,7 @@
final ProcessReceiverRecord receivers = app.mReceivers;
final ProcessProfileRecord profile = app.mProfile;
final ProcessProviderRecord providers = app.mProviders;
- app.makeActive(mock(IApplicationThread.class), mService.mProcessStats);
+ app.makeActive(mock(ApplicationThreadDeferred.class), mService.mProcessStats);
app.setLastActivityTime(mLastActivityTime);
app.setKilledByAm(mKilledByAm);
app.setIsolatedEntryPoint(mIsolatedEntryPoint);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 89c67d5..3572d23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -193,7 +193,7 @@
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
throws Exception {
- final IApplicationThread thread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 5f12677..1ff4a27 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -529,7 +529,7 @@
@SuppressWarnings("GuardedBy")
private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
String packageName) {
- final IApplicationThread appThread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = mock(IBinder.class);
final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0,
procState, adj, cap, 0L, 0L, packageName, packageName, mAms);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
index 7ec27be..3c43636 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -145,7 +145,7 @@
name, // processName
name, // packageName
mAms);
- app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+ app.makeActive(mock(ApplicationThreadDeferred.class), mAms.mProcessStats);
mProcessList.updateLruProcessLocked(app, false, null);
final long now = SystemClock.uptimeMillis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 396f4da..bf7e3a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -525,7 +525,7 @@
@Test
public void
- chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsIgnore()
+ chooseRestorePolicy_flagOnNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsAccept()
throws Exception {
mSetFlagsRule.enableFlags(
@@ -540,7 +540,8 @@
FileMetadata info = new FileMetadata();
info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
- PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -559,7 +560,7 @@
@Test
public void
- chooseRestorePolicy_flagOffNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsAccept()
+ chooseRestorePolicy_flagOffNotRestoreAnyVersionVToURestoreAndInAllowlist_returnsIgnore()
throws Exception {
mSetFlagsRule.disableFlags(
@@ -574,7 +575,8 @@
FileMetadata info = new FileMetadata();
info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
- PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -608,7 +610,8 @@
FileMetadata info = new FileMetadata();
info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1;
- PackageInfo packageInfo = createNonRestoreAnyVersionUPackage();
+ PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -716,6 +719,36 @@
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
}
+ @Test
+ public void
+ chooseRestorePolicy_allowlistNotSetNotRestoreAnyVersionVersionMismatch_returnsIgnore()
+ throws Exception {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ TarBackupReader tarBackupReader = createTarBackupReader();
+
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 2;
+
+ PackageInfo packageInfo = createNonRestoreAnyVersionPackage(
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE + 1);
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId, mContext);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
+ }
+
private TarBackupReader createTarBackupReader() throws Exception {
InputStream inputStream = mContext.getResources().openRawResource(
R.raw.backup_telephony_no_password);
@@ -726,7 +759,7 @@
return tarBackupReader;
}
- private PackageInfo createNonRestoreAnyVersionUPackage(){
+ private PackageInfo createNonRestoreAnyVersionPackage(int versionCode) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
@@ -740,7 +773,7 @@
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
null,
null));
- packageInfo.versionCode = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+ packageInfo.versionCode = versionCode;
return packageInfo;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
new file mode 100644
index 0000000..127d3e8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -0,0 +1,58 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_mainline_modularization",
+}
+
+android_test {
+ name: "CrashRecoveryModuleTests",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "truth",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml
new file mode 100644
index 0000000..067f116
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.crashrecovery">
+
+ <uses-sdk android:targetSdkVersion="35" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.crashrecovery"
+ android:label="Frameworks crashrecovery module test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
new file mode 100644
index 0000000..7b06ebe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs Crashrecovery Module Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="CrashRecoveryModuleTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="CrashRecoveryModuleTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.crashrecovery" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java
new file mode 100644
index 0000000..c481f84
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryModuleTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import static com.android.server.SystemService.PHASE_ACTIVITY_MANAGER_READY;
+import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
+import com.android.server.RescueParty;
+import com.android.server.crashrecovery.CrashRecoveryModule.Lifecycle;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Test CrashRecoveryModule.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrashRecoveryModuleTest {
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule;
+
+ private MockitoSession mSession;
+ private Lifecycle mLifecycle;
+
+ @Mock PackageWatchdog mPackageWatchdog;
+
+ @Before
+ public void setup() {
+ Context context = ApplicationProvider.getApplicationContext();
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(PackageWatchdog.class)
+ .mockStatic(RescueParty.class)
+ .startMocking();
+ when(PackageWatchdog.getInstance(context)).thenReturn(mPackageWatchdog);
+ ExtendedMockito.doNothing().when(() -> RescueParty.registerHealthObserver(context));
+ mLifecycle = new Lifecycle(context);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testLifecycleServiceStart() {
+ mLifecycle.onStart();
+
+ verify(mPackageWatchdog, times(1)).noteBoot();
+ ExtendedMockito.verify(() -> RescueParty.registerHealthObserver(any()),
+ Mockito.times(1));
+ }
+
+ @Test
+ public void testLifecycleServiceOnBootPhase() {
+ doNothing().when(mPackageWatchdog).onPackagesReady();
+
+ mLifecycle.onBootPhase(PHASE_ACTIVITY_MANAGER_READY);
+ verify(mPackageWatchdog, never()).onPackagesReady();
+
+ mLifecycle.onBootPhase(PHASE_THIRD_PARTY_APPS_CAN_START);
+ verify(mPackageWatchdog, times(1)).onPackagesReady();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 0000000..8337fd2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 00daf41..1a398c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.trust;
+import static android.security.Flags.FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -73,6 +75,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
@@ -91,6 +95,7 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -101,6 +106,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -112,7 +118,16 @@
import java.util.List;
import java.util.Map;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
public class TrustManagerServiceTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH);
+ }
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -122,6 +137,9 @@
.build();
@Rule
+ public final SetFlagsRule mSetFlagsRule;
+
+ @Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
@@ -162,6 +180,10 @@
private ITrustManager mTrustManager;
private ActivityManagerInternal mPreviousActivityManagerInternal;
+ public TrustManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT, flags);
+ }
+
@Before
public void setUp() throws Exception {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
@@ -594,11 +616,27 @@
}
private void attemptSuccessfulUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationSucceeded(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ }
}
private void attemptFailedUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationFailed(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ }
}
private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index 362607b..b13fc53 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -355,7 +355,7 @@
assertThat(stats.getNumBytesRx()).isEqualTo(13321);
assertThat(stats.getNumPacketsTx()).isEqualTo(263);
assertThat(stats.getNumBytesTx()).isEqualTo(7234);
- assertThat(stats.getScanTimeMillis()).isEqualTo(2200);
+ assertThat(stats.getScanTimeMillis()).isEqualTo(200);
assertThat(stats.getRxTimeMillis()).isEqualTo(6000);
assertThat(stats.getTxTimeMillis()).isEqualTo(1000);
assertThat(stats.getIdleTimeMillis()).isEqualTo(300);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index ca30551..62fa951 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -152,6 +152,7 @@
@Test
public void onSwitchToAnotherUser_userStateClearedNonDefaultValues() {
+ String componentNameString = COMPONENT_NAME.flattenToString();
mUserState.getBoundServicesLocked().add(mMockConnection);
mUserState.getBindingServicesLocked().add(COMPONENT_NAME);
mUserState.setLastSentClientStateLocked(
@@ -162,10 +163,13 @@
mUserState.setInteractiveUiTimeoutLocked(30);
mUserState.mEnabledServices.add(COMPONENT_NAME);
mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
- mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), HARDWARE);
- mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), SOFTWARE);
- mUserState.updateShortcutTargetsLocked(Set.of(COMPONENT_NAME.flattenToString()), GESTURE);
- mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), HARDWARE);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), SOFTWARE);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), GESTURE);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), QUICK_SETTINGS);
+ mUserState.updateA11yTilesInQsPanelLocked(
+ Set.of(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME));
+ mUserState.setTargetAssignedToAccessibilityButton(componentNameString);
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
@@ -189,6 +193,8 @@
assertTrue(mUserState.getShortcutTargetsLocked(HARDWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
+ assertTrue(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS).isEmpty());
+ assertTrue(mUserState.getA11yQsTilesInQsPanel().isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index dc8d239..0def516 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -16,6 +16,8 @@
package com.android.server.accessibility
+import android.util.MathUtils.sqrt
+
import android.companion.virtual.VirtualDeviceManager
import android.companion.virtual.VirtualDeviceParams
import android.content.Context
@@ -59,6 +61,7 @@
companion object {
const val DISPLAY_ID = 1
const val DEVICE_ID = 123
+ const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f
// This delay is required for key events to be sent and handled correctly.
// The handler only performs a move/scroll event if it receives the key event
// at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
@@ -113,8 +116,7 @@
Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
- mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
- testLooper.looper, DISPLAY_ID)
+ mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
// VirtualMouse is created on a separate thread.
// Wait for VirtualMouse to be created before running tests
TimeUnit.MILLISECONDS.sleep(20L)
@@ -145,7 +147,7 @@
fun whenMouseDirectionalKeyIsPressed_relativeEventIsSent() {
// There should be some delay between the downTime of the key event and calling onKeyEvent
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
- val keyCode = MouseKeysInterceptor.MouseKeyEvent.DOWN_MOVE.getKeyCodeValue()
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.DIAGONAL_DOWN_LEFT_MOVE.keyCodeValue
val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
keyCode, 0, 0, DEVICE_ID, 0)
@@ -153,14 +155,15 @@
testLooper.dispatchAll()
// Verify the sendRelativeEvent method is called once and capture the arguments
- verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(1.8f))
+ verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
+ arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
}
@Test
fun whenClickKeyIsPressed_buttonEventIsSent() {
// There should be some delay between the downTime of the key event and calling onKeyEvent
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
- val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.keyCodeValue
val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
keyCode, 0, 0, DEVICE_ID, 0)
mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -179,7 +182,7 @@
@Test
fun whenHoldKeyIsPressed_buttonEventIsSent() {
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
- val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.getKeyCodeValue()
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.keyCodeValue
val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
keyCode, 0, 0, DEVICE_ID, 0)
mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -195,7 +198,7 @@
@Test
fun whenReleaseKeyIsPressed_buttonEventIsSent() {
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
- val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.getKeyCodeValue()
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.keyCodeValue
val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
keyCode, 0, 0, DEVICE_ID, 0)
mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -209,18 +212,38 @@
}
@Test
- fun whenScrollUpKeyIsPressed_scrollEventIsSent() {
+ fun whenScrollToggleOn_ScrollUpKeyIsPressed_scrollEventIsSent() {
// There should be some delay between the downTime of the key event and calling onKeyEvent
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
- val keyCode = MouseKeysInterceptor.MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+ val keyCodeScrollToggle = MouseKeysInterceptor.MouseKeyEvent.SCROLL_TOGGLE.keyCodeValue
+ val keyCodeScroll = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
+
+ val scrollToggleDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScrollToggle, 0, 0, DEVICE_ID, 0)
+ val scrollDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScroll, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(scrollToggleDownEvent, 0)
+ mouseKeysInterceptor.onKeyEvent(scrollDownEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendScrollEvent method is called once and capture the arguments
+ verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ }
+
+ @Test
+ fun whenScrollToggleOff_DirectionalUpKeyIsPressed_RelativeEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCode = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
keyCode, 0, 0, DEVICE_ID, 0)
mouseKeysInterceptor.onKeyEvent(downEvent, 0)
testLooper.dispatchAll()
- // Verify the sendScrollEvent method is called once and capture the arguments
- verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+ // Verify the sendRelativeEvent method is called once and capture the arguments
+ verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(-MOUSE_POINTER_MOVEMENT_STEP))
}
private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 758c84a..ef9580c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 2cb02bd..4645156 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 037c3c0..b7100ea 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 27b552f..746645a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class));
+ mock(AudioServerPermissionProvider.class), r -> r.run());
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 8e34ee1..e45ab31 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
@NonNull PermissionEnforcer enforcer,
AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer, permissionProvider);
+ audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
}
public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 10f4308..599a3b8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -396,7 +396,7 @@
}
@Test
- public void cecDevices_tracking_updatesPhysicalAddress() {
+ public void cecDevices_tracking_updatesPhysicalAddress_add() {
int logicalAddress = Constants.ADDR_PLAYBACK_1;
int initialPhysicalAddress = 0x1000;
int updatedPhysicalAddress = 0x2000;
@@ -415,11 +415,12 @@
assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress);
assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
- // ADD for physical address first detected
- // UPDATE for updating device with new physical address
+ // Handle case where PA is changed: Update CEC device information by calling
+ // addCecDevice().
assertThat(mDeviceEventListenerStatuses).containsExactly(
HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
- HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
index 8863d27..41cb6fd 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,8 @@
public class ContextHubEventLoggerTest {
private static final ContextHubEventLogger sInstance = ContextHubEventLogger.getInstance();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testLogNanoappLoad() {
ContextHubEventLogger.NanoappLoadEvent[] events =
@@ -46,10 +52,10 @@
sInstance.clear();
sInstance.logNanoappLoad(-1, 42, -34, 100, false);
sInstance.logNanoappLoad(0, 123, 321, 001, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -66,10 +72,10 @@
sInstance.clear();
sInstance.logNanoappUnload(-1, 47, false);
sInstance.logNanoappUnload(1, 0xFFFFFFFF, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -90,10 +96,10 @@
sInstance.clear();
sInstance.logMessageFromNanoapp(-123, message1, false);
sInstance.logMessageFromNanoapp(321, message2, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -114,10 +120,54 @@
sInstance.clear();
sInstance.logMessageToNanoapp(888, message1, true);
sInstance.logMessageToNanoapp(999, message2, false);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_RELIABLE_MESSAGE,
+ Flags.FLAG_RELIABLE_MESSAGE_IMPLEMENTATION})
+ public void testLogReliableMessageToNanoappStatus() {
+ NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0,
+ new byte[] {0x00, 0x11, 0x22, 0x33});
+ NanoAppMessage message2 = NanoAppMessage.createMessageToNanoApp(0, 1,
+ new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
+ message1.setIsReliable(true);
+ message2.setIsReliable(true);
+ message1.setMessageSequenceNumber(0);
+ message2.setMessageSequenceNumber(1);
+
+ ContextHubEventLogger.NanoappMessageEvent[] events =
+ new ContextHubEventLogger.NanoappMessageEvent[] {
+ new ContextHubEventLogger.NanoappMessageEvent(23, 888, message1, true),
+ new ContextHubEventLogger.NanoappMessageEvent(34, 999, message2, false)
+ };
+ String[] eventStrings = generateEventDumpStrings(events);
+
+ // log events and test sInstance.toString() contains event details
+ sInstance.clear();
+ sInstance.logMessageToNanoapp(888, message1, true);
+ sInstance.logMessageToNanoapp(999, message2, false);
+ String instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+
+ // set the error codes for the events and verify
+ sInstance.logReliableMessageToNanoappStatus(0, (byte) 0x02);
+ sInstance.logReliableMessageToNanoappStatus(1, (byte) 0x03);
+ events[0].setErrorCode((byte) 0x02);
+ events[1].setErrorCode((byte) 0x03);
+ eventStrings = generateEventDumpStrings(events);
+
+ instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -134,10 +184,10 @@
sInstance.clear();
sInstance.logContextHubRestart(1);
sInstance.logContextHubRestart(2);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index e64397d..316b5fa 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server.media.projection;
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING;
@@ -50,6 +51,7 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions.LaunchCookie;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
@@ -66,7 +68,9 @@
import android.os.RemoteException;
import android.os.UserHandle;
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.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
@@ -81,6 +85,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -151,6 +156,9 @@
private ContentRecordingSession mWaitingDisplaySession =
createDisplaySession(DEFAULT_DISPLAY);
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private ActivityManagerInternal mAmInternal;
@Mock
@@ -158,6 +166,8 @@
@Mock
private PackageManager mPackageManager;
@Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
private IMediaProjectionWatcherCallback mWatcherCallback;
@Mock
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -177,6 +187,7 @@
mContext = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE));
mClock = new OffsettableClock.Stopped();
mWaitingDisplaySession.setWaitingForConsent(true);
@@ -246,6 +257,39 @@
assertThat(stoppedCallback2).isFalse();
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked() throws Exception {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+ .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ assertThat(mService.getActiveProjectionInfo()).isNull();
+ assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_packageAllowlisted()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager)
+ .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
@@ -317,6 +361,48 @@
assertThat(secondProjection).isNotEqualTo(projection);
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testKeyguardLocked_stopsActiveProjection() throws Exception {
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+
+ assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+ .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+ service.onKeyguardLockedStateChanged(true);
+
+ verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN);
+ assertThat(service.getActiveProjectionInfo()).isNull();
+ assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
+ throws NameNotFoundException {
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+
+ assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+ service.onKeyguardLockedStateChanged(true);
+
+ verifyZeroInteractions(mMediaProjectionMetricsLogger);
+ assertThat(service.getActiveProjectionInfo()).isNotNull();
+ }
+
@Test
public void stop_noActiveProjections_doesNotLog() throws Exception {
MediaProjectionManagerService service =
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 02d3b59..d714db99 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1060,6 +1060,23 @@
assertThrows(SecurityException.class, userProps::getAlwaysVisible);
}
+ /**
+ * Test that UserManager.getUserProperties throws the IllegalArgumentException for unsupported
+ * arguments such as UserHandle.NULL, UserHandle.CURRENT or UserHandle.ALL.
+ **/
+ @MediumTest
+ @Test
+ public void testThrowUserPropertiesForUnsupportedUserHandles() throws Exception {
+ assertThrows(IllegalArgumentException.class, () ->
+ mUserManager.getUserProperties(UserHandle.of(UserHandle.USER_NULL)));
+ assertThrows(IllegalArgumentException.class, () ->
+ mUserManager.getUserProperties(UserHandle.CURRENT));
+ assertThrows(IllegalArgumentException.class, () ->
+ mUserManager.getUserProperties(UserHandle.CURRENT_OR_SELF));
+ assertThrows(IllegalArgumentException.class, () ->
+ mUserManager.getUserProperties(UserHandle.ALL));
+ }
+
// Make sure only max managed profiles can be created
@MediumTest
@Test
@@ -1845,6 +1862,25 @@
assertThat(profilesExcludingHidden).asList().doesNotContain(profile.id);
}
+ /**
+ * Test that UserManager.isQuietModeEnabled return false for unsupported
+ * arguments such as UserHandle.NULL, UserHandle.CURRENT or UserHandle.ALL.
+ **/
+ @MediumTest
+ @Test
+ public void testQuietModeEnabledForUnsupportedUserHandles() throws Exception {
+ assumeManagedUsersSupported();
+ final int mainUserId = mUserManager.getMainUser().getIdentifier();
+ UserInfo userInfo = createProfileForUser("Profile",
+ UserManager.USER_TYPE_PROFILE_MANAGED, mainUserId);
+ mUserManager.requestQuietModeEnabled(true, userInfo.getUserHandle());
+ assertThat(mUserManager.isQuietModeEnabled(userInfo.getUserHandle())).isTrue();
+ assertThat(mUserManager.isQuietModeEnabled(UserHandle.of(UserHandle.USER_NULL))).isFalse();
+ assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT)).isFalse();
+ assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT_OR_SELF)).isFalse();
+ assertThat(mUserManager.isQuietModeEnabled(UserHandle.ALL)).isFalse();
+ }
+
private String generateLongString() {
String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+ "Name Test Name Test Name Test Name "; //String of length 100
diff --git a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
index 698f094..6b32be0 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakefulnessSessionObserverTest.java
@@ -16,9 +16,14 @@
package com.android.server.power;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON;
import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_UNKNOWN;
import static com.android.server.power.ScreenTimeoutOverridePolicy.RELEASE_REASON_USER_ACTIVITY_TOUCH;
@@ -27,6 +32,10 @@
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_CANCEL_USER_INTERACTION;
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_SUCCESS;
import static com.android.server.power.WakefulnessSessionObserver.OVERRIDE_OUTCOME_TIMEOUT_USER_INITIATED_REVERT;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_BRIGHT_INITIATED_REVERT;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_BRIGHT_UNDIM;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_OFF_POWER_BUTTON;
+import static com.android.server.power.WakefulnessSessionObserver.POLICY_REASON_OFF_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -40,18 +49,24 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
import org.junit.After;
import org.junit.Before;
@@ -65,25 +80,18 @@
public class WakefulnessSessionObserverTest {
private static final int DEFAULT_SCREEN_OFF_TIMEOUT_MS = 30000;
private static final int OVERRIDE_SCREEN_OFF_TIMEOUT_MS = 15000;
+ private static final int DISPLAY_PORT = 0xFF;
+ private static final long DISPLAY_MODEL = 0xEEEEEEEEL;
private WakefulnessSessionObserver mWakefulnessSessionObserver;
private Context mContext;
private OffsettableClock mTestClock;
@Mock
private WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
mWakefulnessSessionFrameworkStatsLogger;
- private WakefulnessSessionObserver.Injector mInjector =
- new WakefulnessSessionObserver.Injector() {
- @Override
- WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
- getWakefulnessSessionFrameworkStatsLogger() {
- return mWakefulnessSessionFrameworkStatsLogger;
- }
- @Override
- WakefulnessSessionObserver.Clock getClock() {
- return mTestClock::now;
- }
- };
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternal;
+ private TestHandler mHandler;
@Before
public void setUp() {
mTestClock = new OffsettableClock.Stopped();
@@ -95,7 +103,7 @@
final Resources res = spy(mContext.getResources());
doReturn(OVERRIDE_SCREEN_OFF_TIMEOUT_MS).when(res).getInteger(
- com.android.internal.R.integer.config_screenTimeoutOverride);
+ R.integer.config_screenTimeoutOverride);
when(mContext.getResources()).thenReturn(res);
FakeSettingsProvider.clearSettingsProvider();
MockContentResolver mockContentResolver = new MockContentResolver();
@@ -104,7 +112,32 @@
Settings.System.putIntForUser(mockContentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
DEFAULT_SCREEN_OFF_TIMEOUT_MS, UserHandle.USER_CURRENT);
- mWakefulnessSessionObserver = new WakefulnessSessionObserver(mContext, mInjector);
+ final DisplayInfo info = new DisplayInfo();
+ info.address = DisplayAddress.fromPortAndModel(DISPLAY_PORT, DISPLAY_MODEL);
+ mHandler = new TestHandler(null);
+ mWakefulnessSessionObserver = new WakefulnessSessionObserver(
+ mContext, new WakefulnessSessionObserver.Injector() {
+ @Override
+ WakefulnessSessionObserver.WakefulnessSessionFrameworkStatsLogger
+ getWakefulnessSessionFrameworkStatsLogger() {
+ return mWakefulnessSessionFrameworkStatsLogger;
+ }
+ @Override
+ WakefulnessSessionObserver.Clock getClock() {
+ return mTestClock::now;
+ }
+ @Override
+ Handler getHandler() {
+ return mHandler;
+ }
+ @Override
+ DisplayManagerInternal getDisplayManagerInternal() {
+ when(mDisplayManagerInternal.getDisplayInfo(DEFAULT_DISPLAY))
+ .thenReturn(info);
+ return mDisplayManagerInternal;
+ }
+ }
+ );
}
@After
@@ -317,6 +350,167 @@
DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default timeout ms
}
+ @Test
+ public void testOnScreenPolicyUpdate_OffByTimeout() {
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_ATTENTION;
+ long userActivityTimestamp = mTestClock.now();
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+ WAKE_REASON_POWER_BUTTON, mTestClock.now());
+ int advancedTime = 5;
+ advanceTime(advancedTime);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), DEFAULT_DISPLAY_GROUP,
+ POLICY_OFF);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+ GO_TO_SLEEP_REASON_TIMEOUT, mTestClock.now());
+
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logDimEvent(
+ DISPLAY_PORT, // physical display port id
+ POLICY_REASON_OFF_TIMEOUT, // policy reason
+ userActivity, // last user activity event
+ advancedTime, // last user activity timestamp
+ advancedTime, // dim duration ms
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+ }
+
+ @Test
+ public void testOnScreenPolicyUpdate_NoLogging_NotDefaultDisplayGroup() {
+ int powerGroupId = 1;
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_ATTENTION;
+ long userActivityTimestamp = mTestClock.now();
+ int advancedTime = 5;
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTimestamp, powerGroupId, userActivity);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), powerGroupId, POLICY_DIM);
+ advanceTime(advancedTime);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), powerGroupId,
+ POLICY_OFF);
+
+ verify(mWakefulnessSessionFrameworkStatsLogger, never())
+ .logDimEvent(
+ DISPLAY_PORT, // physical display port id
+ POLICY_REASON_OFF_TIMEOUT, // policy reason
+ userActivity, // last user activity event
+ advancedTime, // last user activity timestamp
+ advancedTime, // dim duration ms
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+ }
+
+ @Test
+ public void testOnScreenPolicyUpdate_OffByPowerButton() {
+ // ----- initialize start -----
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+ WAKE_REASON_POWER_BUTTON, mTestClock.now());
+
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY;
+ long userActivityTimestamp = mTestClock.now();
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+ // ----- initialize end -----
+
+ int dimDuration = 500;
+ advanceTime(dimDuration);
+ int userActivityDuration = dimDuration;
+ mWakefulnessSessionObserver.notifyUserActivity(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, PowerManager.USER_ACTIVITY_EVENT_BUTTON);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+ GO_TO_SLEEP_REASON_POWER_BUTTON, mTestClock.now());
+
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logDimEvent(
+ DISPLAY_PORT, // physical display port id
+ POLICY_REASON_OFF_POWER_BUTTON, // policy reason
+ userActivity, // last user activity event
+ userActivityDuration, // last user activity timestamp
+ dimDuration, // dim duration ms
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+ assertThat(mHandler.getPendingMessages()).isEmpty();
+ }
+
+ @Test
+ public void testOnScreenPolicyUpdate_Undim() {
+ // ----- initialize start -----
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_TOUCH;
+ long userActivityTimestamp = mTestClock.now();
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+ mWakefulnessSessionObserver.mPowerGroups.get(DEFAULT_DISPLAY_GROUP).mIsInteractive = true;
+ // ----- initialize end -----
+
+ int dimDurationMs = 5;
+ advanceTime(dimDurationMs);
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT);
+
+ int expectedLastUserActivityTimeMs = (int) (mTestClock.now() - userActivityTimestamp);
+
+ mHandler.flush();
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logDimEvent(
+ DISPLAY_PORT, // physical display port id
+ POLICY_REASON_BRIGHT_UNDIM, // policy reason
+ userActivity, // last user activity event
+ expectedLastUserActivityTimeMs, // last user activity timestamp
+ dimDurationMs, // dim duration ms
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+ }
+
+ @Test
+ public void testOnScreenPolicyUpdate_BrightInitiatedRevert() {
+ // ----- initialize start -----
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_DIM);
+ int dimDurationMs = 500;
+ advanceTime(dimDurationMs);
+ int userActivity = PowerManager.USER_ACTIVITY_EVENT_BUTTON;
+ long userActivityTimestamp = mTestClock.now();
+ mWakefulnessSessionObserver.notifyUserActivity(
+ userActivityTimestamp, DEFAULT_DISPLAY_GROUP, userActivity);
+ int userActivityTime = 5;
+ advanceTime(userActivityTime);
+ dimDurationMs += userActivityTime;
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(
+ mTestClock.now(), DEFAULT_DISPLAY_GROUP, POLICY_OFF);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_ASLEEP,
+ GO_TO_SLEEP_REASON_POWER_BUTTON, mTestClock.now());
+
+ mWakefulnessSessionObserver.mPowerGroups.get(DEFAULT_DISPLAY_GROUP)
+ .mPastDimDurationMs = dimDurationMs;
+ // ----- initialize end -----
+
+ int advancedTime = 5;
+ advanceTime(advancedTime); // shorter than 5000 ms
+ userActivityTime += advancedTime;
+ mWakefulnessSessionObserver.onScreenPolicyUpdate(mTestClock.now(), DEFAULT_DISPLAY_GROUP,
+ POLICY_BRIGHT);
+ mWakefulnessSessionObserver.onWakefulnessChangeStarted(
+ DEFAULT_DISPLAY_GROUP, PowerManagerInternal.WAKEFULNESS_AWAKE,
+ WAKE_REASON_POWER_BUTTON, mTestClock.now());
+
+ verify(mWakefulnessSessionFrameworkStatsLogger)
+ .logDimEvent(
+ DISPLAY_PORT, // physical display port id
+ POLICY_REASON_BRIGHT_INITIATED_REVERT, // policy reason
+ userActivity, // last user activity event
+ userActivityTime, // last user activity timestamp
+ dimDurationMs, // dim duration ms
+ DEFAULT_SCREEN_OFF_TIMEOUT_MS); // default Timeout Ms
+ }
+
private void advanceTime(long timeMs) {
mTestClock.fastForward(timeMs);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index bf47816..1decd36 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -151,7 +151,6 @@
private HintManagerService mService;
private ChannelConfig mConfig;
- private ApplicationInfo mApplicationInfo;
private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
return new Answer<Long>() {
@@ -168,12 +167,12 @@
mConfig = new ChannelConfig();
mConfig.readFlagBitmask = 1;
mConfig.writeFlagBitmask = 2;
- mApplicationInfo = new ApplicationInfo();
- mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
- .thenReturn(mApplicationInfo);
+ .thenReturn(applicationInfo);
when(mNativeWrapperMock.halGetHintSessionPreferredRate())
.thenReturn(DEFAULT_HINT_PREFERRED_RATE);
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 28a5db9..e06d939 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -223,6 +223,10 @@
Settings.System.putInt(getContext().getContentResolver(),
Settings.System.NOTIFICATION_LIGHT_PULSE, 1);
+ // Enable notification cooldown independent of device Settings
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 1);
+
Resources resources = spy(getContext().getResources());
when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true);
when(resources.getBoolean(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index ff1308c..1c8cb8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -137,4 +137,9 @@
checkInRange(i);
return mChanges.get(i).getActiveRuleTypes();
}
+
+ public int getChangeOrigin(int i) throws IllegalArgumentException {
+ checkInRange(i);
+ return mChanges.get(i).getChangeOrigin();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7bb633e..4bbbc2b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -818,7 +818,7 @@
// 1. Current ringer is normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
- Policy totalSilence = new Policy(0,0,0);
+ Policy totalSilence = new Policy(0, 0, 0);
mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -873,7 +873,7 @@
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.setNotificationPolicy(new Policy(0,0,0), UPDATE_ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), UPDATE_ORIGIN_APP, 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
UPDATE_ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
@@ -1065,9 +1065,10 @@
@Test
public void testParcelConfig() {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
- | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
- | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
- PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE), UPDATE_ORIGIN_UNKNOWN,
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
+ UPDATE_ORIGIN_UNKNOWN,
1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
@@ -1085,13 +1086,14 @@
@Test
public void testWriteXml() throws Exception {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
- | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
- | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
- PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE, CONVERSATION_SENDERS_ANYONE),
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
+ CONVERSATION_SENDERS_ANYONE),
UPDATE_ORIGIN_UNKNOWN, 1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
.build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
@@ -2210,7 +2212,7 @@
customDefaultRule.name = "Schedule Default Rule";
customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
ScheduleInfo scheduleInfo = new ScheduleInfo();
- scheduleInfo.days = new int[] { Calendar.SUNDAY };
+ scheduleInfo.days = new int[]{Calendar.SUNDAY};
scheduleInfo.startHour = 18;
scheduleInfo.endHour = 19;
customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(scheduleInfo);
@@ -3027,7 +3029,7 @@
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
@@ -3060,6 +3062,9 @@
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ // change origin should be populated only under modes_ui
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ (Flags.modesApi() && Flags.modesUi()) ? UPDATE_ORIGIN_USER : 0);
// and from turning zen mode off:
// - event ID: DND_TURNED_OFF
@@ -3082,6 +3087,8 @@
} else {
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
}
@Test
@@ -3098,17 +3105,21 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
+ // Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
+ // that look like they're coming from the system are attributed to the app, but when
+ // modes_ui is true, we opt to trust the provided change origin.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ CUSTOM_PKG_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
Process.SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3118,7 +3129,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
Process.SYSTEM_UID);
// Event 3: turn on the system rule
@@ -3128,7 +3139,7 @@
// Event 4: "User" deletes the rule
mZenModeHelper.removeAutomaticZenRule(systemId,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3151,9 +3162,13 @@
assertFalse(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// When the automatic rule is disabled, this should turn off zen mode and also count as a
// user action. We don't care what the consolidated policy is when DND turns off.
+ // When modes_ui is true, this event should look like a user action attributed to the
+ // specific app.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
mZenModeEventLogger.getEventId(1));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
@@ -3161,12 +3176,15 @@
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+ assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
+ Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
if (Flags.modesApi()) {
assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
} else {
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
// When the system rule is enabled, this counts as an automatic action that comes from the
// system and turns on DND
@@ -3176,6 +3194,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : 0);
// When the system rule is deleted, we consider this a user action that turns DND off
// (again)
@@ -3185,6 +3205,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
assertTrue(mZenModeEventLogger.getIsUserAction(3));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
+ assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
}
@Test
@@ -3238,6 +3260,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
// Automatic rule turned off automatically by app:
// - event ID: DND_TURNED_OFF
@@ -3249,6 +3273,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// Automatic rule turned on automatically by app:
// - event ID: DND_TURNED_ON
@@ -3261,6 +3287,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// Automatic rule turned off automatically by the user:
// - event ID: DND_TURNED_ON
@@ -3272,6 +3300,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
assertTrue(mZenModeEventLogger.getIsUserAction(3));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
+ assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
}
@Test
@@ -3335,7 +3365,7 @@
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3345,7 +3375,7 @@
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3395,7 +3425,7 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
- assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+ assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
// Event 2: rule 2 turns on. This should not change anything about the policy, so the only
@@ -3404,7 +3434,7 @@
mZenModeEventLogger.getEventId(1));
assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+ assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
// Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
@@ -3482,9 +3512,11 @@
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
// re-evaluated to the one associated with CUSTOM_PKG_NAME.
+ // When modes_ui is true: we expect the change origin to be the source of truth.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ Process.SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
@@ -3493,7 +3525,7 @@
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
- // from the system-provided one.
+ // from the system-provided one unless modes_ui is true.
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
@@ -3504,6 +3536,7 @@
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
// the system, we keep the UID info.
+ // Note that this probably shouldn't be able to occur in real scenarios.
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
UPDATE_ORIGIN_APP, 12345);
@@ -3528,11 +3561,13 @@
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
// Third event: disable rule 1. This looks like a user action so UID should be left alone.
+ // When modes_ui is true, we assign log this user action with the app that owns the rule.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId(),
mZenModeEventLogger.getEventId(2));
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
assertTrue(mZenModeEventLogger.getIsUserAction(2));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getPackageUid(2)).isEqualTo(
+ Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
// Fourth event: turns on manual mode. Doesn't change effective policy so this is just a
// change in active rules. Confirm that the package UID is left unchanged.
@@ -6202,7 +6237,7 @@
public void setManualZenRuleDeviceEffects_noPreexistingMode() {
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
- .build();
+ .build();
mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
@@ -6339,21 +6374,21 @@
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
- Parcel p = Parcel.obtain();
- try {
- zr.writeToParcel(p, 0);
- p.setDataPosition(0);
- ZenRule copy = new ZenRule(p);
- copy.creationTime = 0;
- copy.userModifiedFields = 0;
- copy.zenPolicyUserModifiedFields = 0;
- copy.zenDeviceEffectsUserModifiedFields = 0;
- return copy;
- } finally {
- p.recycle();
- }
- },
- "Ignoring timestamp and userModifiedFields");
+ Parcel p = Parcel.obtain();
+ try {
+ zr.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ZenRule copy = new ZenRule(p);
+ copy.creationTime = 0;
+ copy.userModifiedFields = 0;
+ copy.zenPolicyUserModifiedFields = 0;
+ copy.zenDeviceEffectsUserModifiedFields = 0;
+ return copy;
+ } finally {
+ p.recycle();
+ }
+ },
+ "Ignoring timestamp and userModifiedFields");
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 777f618..6e6b70d 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
<uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"/>
<uses-permission android:name="android.permission.DUMP"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
new file mode 100644
index 0000000..88419e9
--- /dev/null
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<bookmarks>
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
new file mode 100644
index 0000000..8c375d4
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -0,0 +1,167 @@
+/*
+ * 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.policy;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+/**
+ * Test class for {@link ModifierShortcutManager}.
+ *
+ * Build/Install/Run:
+ * atest ModifierShortcutManagerTests
+ */
+
+@SmallTest
+public class ModifierShortcutManagerTests {
+ private ModifierShortcutManager mModifierShortcutManager;
+ private Handler mHandler;
+ private Context mContext;
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ mHandler = new Handler(Looper.getMainLooper());
+ mContext = spy(getInstrumentation().getTargetContext());
+ mResources = spy(mContext.getResources());
+
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
+
+ mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+ }
+
+ @Test
+ public void test_getApplicationLaunchKeyboardShortcuts() {
+ KeyboardShortcutGroup group =
+ mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
+ assertEquals(8, group.getItems().size());
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_appIntent() {
+ Intent mockIntent = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label");
+ mockActivityInfo.packageName = "android";
+ when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon);
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, true);
+
+ assertEquals("label", info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON, info.getModifiers());
+
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_resolverIntent() {
+ Intent mockIntent = mock(Intent.class);
+ Intent mockSelector = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName();
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+ when(mockIntent.getSelector()).thenReturn(mockSelector);
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton(Intent.CATEGORY_APP_BROWSER));
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false);
+
+ assertEquals(mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON, info.getModifiers());
+
+ // validate that an unknown category that we can't present a label to the user for
+ // returns null shortcut info.
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton("not_a_category"));
+ assertEquals(null, mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false));
+ }
+
+ @Test
+ public void test_getIntentCategoryLabel() {
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_BROWSER));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CONTACTS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_EMAIL));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALENDAR));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MAPS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MUSIC));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MESSAGING));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALCULATOR));
+ assertEquals(null, ModifierShortcutManager.getIntentCategoryLabel(mContext, "foo"));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0ed02dd..59b08a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -65,7 +65,8 @@
private static final SparseArray<String> INTENT_SHORTCUTS = new SparseArray<>();
private static final SparseArray<String> ROLE_SHORTCUTS = new SparseArray<>();
static {
- // These shortcuts should align with those defined in bookmarks.xml
+ // These shortcuts should align with those defined in
+ // services/tests/wmtests/res/xml/bookmarks.xml
INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index dff4984..f5c8fb8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -49,6 +49,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -102,6 +103,9 @@
doReturn(mResources).when(mContext).getResources();
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+ doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
}
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 eb8825c..b4505fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -684,7 +684,8 @@
// Asserts fixed orientation request is not ignored, and the orientation is changed.
assertNotEquals(activityCurOrientation, activity.getConfiguration().orientation);
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
@@ -717,7 +718,8 @@
// Relaunching the app should still respect the orientation request.
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index f79cdc1..867f01f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -143,8 +143,8 @@
}
void setLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) {
- doReturn(enabled).when(mActivityStack.top())
- .isLetterboxedForFixedOrientationAndAspectRatio();
+ doReturn(enabled).when(mActivityStack.top().mAppCompatController
+ .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
}
void enableTreatmentForTopActivity(boolean enabled) {
@@ -163,8 +163,8 @@
}
void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
- doReturn(enabled).when(mActivityStack.top()
- .mLetterboxUiController).shouldApplyUserMinAspectRatioOverride();
+ doReturn(enabled).when(mActivityStack.top().mAppCompatController
+ .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
}
void setShouldCreateCompatDisplayInsets(boolean enabled) {
@@ -172,13 +172,14 @@
}
void setShouldApplyUserFullscreenOverride(boolean enabled) {
- doReturn(enabled).when(mActivityStack.top()
- .mLetterboxUiController).shouldApplyUserFullscreenOverride();
+ doReturn(enabled).when(mActivityStack.top().mAppCompatController
+ .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
}
void setGetUserMinAspectRatioOverrideCode(@PackageManager.UserMinAspectRatio int orientation) {
doReturn(orientation).when(mActivityStack.top()
- .mLetterboxUiController).getUserMinAspectRatioOverrideCode();
+ .mAppCompatController.getAppCompatAspectRatioOverrides())
+ .getUserMinAspectRatioOverrideCode();
}
void setIgnoreOrientationRequest(boolean enabled) {
@@ -401,6 +402,8 @@
mActivityStack.push(activity);
spyOn(activity);
spyOn(activity.mAppCompatController.getTransparentPolicy());
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
spyOn(activity.mLetterboxUiController);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0c1fbf3..af4394a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,15 +25,11 @@
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;
@@ -41,7 +37,6 @@
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;
@@ -51,14 +46,11 @@
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;
@@ -841,353 +833,6 @@
}
@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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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 */);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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 */);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- // 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);
@@ -1253,22 +898,6 @@
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());
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
index a4df034..c9c7e92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
@@ -18,9 +18,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
@@ -30,12 +32,18 @@
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -44,6 +52,7 @@
import java.util.HashSet;
import java.util.Set;
+
/**
* Tests for the {@link BackgroundLaunchProcessController} class.
*
@@ -55,6 +64,10 @@
@RunWith(JUnit4.class)
public class BackgroundLaunchProcessControllerTests {
+
+ @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
Set<IBinder> mActivityStartAllowed = new HashSet<>();
Set<Integer> mHasActiveVisibleWindow = new HashSet<>();
@@ -113,7 +126,8 @@
}
@Test
- public void testAllowedByTokenNoCallback() {
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenNoCallbackOld() {
mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
null);
Binder token = new Binder();
@@ -130,7 +144,26 @@
}
@Test
- public void testAllowedByToken() {
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenNoCallback() {
+ mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
+ null);
+ Binder token = new Binder();
+ mActivityStartAllowed.add(token);
+ mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+ BackgroundStartPrivileges.ALLOW_BAL);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenOld() {
Binder token = new Binder();
mActivityStartAllowed.add(token);
mController.addOrUpdateAllowBackgroundStartPrivileges(token,
@@ -145,7 +178,24 @@
}
@Test
- public void testBoundByForeground() {
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByToken() {
+ Binder token = new Binder();
+ mActivityStartAllowed.add(token);
+ mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+ BackgroundStartPrivileges.ALLOW_BAL);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testBoundByForegroundOld() {
mAppSwitchState = APP_SWITCH_ALLOW;
mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
mHasActiveVisibleWindow.add(999);
@@ -159,6 +209,21 @@
}
@Test
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testBoundByForeground() {
+ mAppSwitchState = APP_SWITCH_ALLOW;
+ mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
+ mHasActiveVisibleWindow.add(999);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_BOUND_BY_FOREGROUND);
+ }
+
+ @Test
public void testForegroundTask() {
mAppSwitchState = APP_SWITCH_ALLOW;
mHasActivityInVisibleTask = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 564c29f..11e6d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -23,7 +23,6 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -176,6 +175,7 @@
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
@@ -185,34 +185,6 @@
}
@Test
- public void testReconnectedToDifferentCamera_activatesCameraCompatModeAndRefresh()
- throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
-
- assertInCameraCompatMode();
- assertActivityRefreshRequested(/* refreshRequested */ true);
- }
-
- @Test
- public void testCameraDisconnected_deactivatesCameraCompatMode() {
- configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE,
- WINDOWING_MODE_FREEFORM);
- // Open camera and test for compat treatment
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode();
-
- // Close camera and test for revert
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
-
- assertNotInCameraCompatMode();
- }
-
- @Test
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index e468fd8..1c8dc05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -256,8 +256,7 @@
}
@Override
- public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public boolean onCameraClosed(@NonNull String cameraId) {
mOnCameraClosedCounter++;
boolean returnValue = mOnCameraClosedReturnValue;
// If false, return false only the first time, so it doesn't fall in the infinite retry
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 5739a04..65aaf60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1871,6 +1871,46 @@
assertFalse(recentsActivity.hasFixedRotationTransform());
}
+ @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
+ @Test
+ public void testRespectNonTopVisibleFixedOrientation() {
+ spyOn(mWm.mLetterboxConfiguration);
+ doReturn(false).when(mWm.mLetterboxConfiguration).isTranslucentLetterboxingEnabled();
+ makeDisplayPortrait(mDisplayContent);
+ final ActivityRecord nonTopVisible = new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setCreateTask(true).build();
+ final ActivityRecord translucentTop = new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setTask(nonTopVisible.getTask()).setVisible(false)
+ .setActivityTheme(android.R.style.Theme_Translucent).build();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+ mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ translucentTop.setVisibility(true);
+ mDisplayContent.updateOrientation();
+ assertEquals("Non-top visible activity must be portrait",
+ Configuration.ORIENTATION_PORTRAIT, nonTopVisible.getConfiguration().orientation);
+ assertEquals("Top translucent activity must be landscape",
+ Configuration.ORIENTATION_LANDSCAPE, translucentTop.getConfiguration().orientation);
+
+ player.start();
+ player.finish();
+ assertEquals("Display must be landscape after the transition is finished",
+ Configuration.ORIENTATION_LANDSCAPE,
+ mDisplayContent.getConfiguration().orientation);
+ assertEquals("Non-top visible activity must still be portrait",
+ Configuration.ORIENTATION_PORTRAIT,
+ nonTopVisible.getConfiguration().orientation);
+
+ translucentTop.finishIfPossible("test", false /* oomAdj */);
+ mDisplayContent.updateOrientation();
+ player.start();
+ player.finish();
+ assertEquals("Display must be portrait after closing the translucent activity",
+ Configuration.ORIENTATION_PORTRAIT,
+ mDisplayContent.getConfiguration().orientation);
+ }
+
@Test
public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
// Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 83ad7b1..708d686 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -214,7 +214,8 @@
final Rect activityBounds = new Rect(mFirstActivity.getBounds());
// DAG is portrait (860x1200), so Task and Activity fill DAG.
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
assertThat(taskBounds).isEqualTo(dagBounds);
assertThat(activityBounds).isEqualTo(taskBounds);
@@ -238,7 +239,8 @@
new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
// DAG is landscape (1200x860), no fixed orientation letterbox
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -262,11 +264,13 @@
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
rotateDisplay(mDisplay, ROTATION_90);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
}
@@ -283,7 +287,8 @@
// DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
// (860x[860x860/1200=616]). Task fills DAG.
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
assertThat(taskBounds).isEqualTo(dagBounds);
assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
@@ -300,7 +305,8 @@
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
}
@Test
@@ -310,7 +316,8 @@
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
}
@Test
@@ -329,7 +336,8 @@
final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
// DAG is landscape (1200x860), no fixed orientation letterbox
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -354,7 +362,8 @@
rotateDisplay(mDisplay, ROTATION_90);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
}
@@ -522,7 +531,8 @@
assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
// Launch portrait on second DAG
@@ -534,13 +544,15 @@
assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
- assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
+ assertThat(mSecondActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
// First activity is letterboxed in portrait as requested.
assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
- assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
+ assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 51b3c48..c42367e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -293,6 +293,7 @@
mainWindow.mInvGlobalScale = 1f;
spyOn(resources);
spyOn(mActivity);
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy());
if (taskbar != null) {
taskbar.setVisible(true);
@@ -301,7 +302,8 @@
doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
doReturn(false).when(mActivity).isInLetterboxAnimation();
doReturn(true).when(mActivity).isVisible();
- doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
doReturn(insets).when(mainWindow).getInsetsState();
doReturn(attrs).when(mainWindow).getAttrs();
doReturn(true).when(mainWindow).isDrawn();
@@ -324,10 +326,11 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
/* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldApplyUserFullscreenOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride());
}
@Test
@@ -337,9 +340,10 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
/* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldApplyUserFullscreenOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride());
}
@Test
@@ -348,16 +352,18 @@
prepareActivityThatShouldApplyUserFullscreenOverride();
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldApplyUserFullscreenOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride());
}
@Test
public void testShouldApplyUserFullscreenOverride_returnsTrue() {
prepareActivityThatShouldApplyUserFullscreenOverride();
- assertTrue(mController.shouldApplyUserFullscreenOverride());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserFullscreenOverride());
}
@Test
@@ -369,18 +375,22 @@
mActivity = setUpActivityWithComponent();
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldEnableUserAspectRatioSettings());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
}
@Test
public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
throws Exception {
- prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mActivity = setUpActivityWithComponent();
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
mController = new LetterboxUiController(mWm, mActivity);
- assertTrue(mController.shouldEnableUserAspectRatioSettings());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
}
@Test
@@ -391,7 +401,8 @@
mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldEnableUserAspectRatioSettings());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
}
@Test
@@ -400,9 +411,10 @@
prepareActivityThatShouldApplyUserMinAspectRatioOverride();
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldEnableUserAspectRatioSettings());
}
@Test
@@ -411,9 +423,10 @@
doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride());
}
@Test
@@ -421,30 +434,35 @@
prepareActivityThatShouldApplyUserMinAspectRatioOverride();
mDisplayContent.setIgnoreOrientationRequest(false);
- assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride());
}
@Test
public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
prepareActivityThatShouldApplyUserMinAspectRatioOverride();
- assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride());
}
@Test
public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() {
prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
- assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride());
}
private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
boolean orientationRequest) {
- spyOn(mController);
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
doReturn(orientationRequest).when(
mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
mDisplayContent.setIgnoreOrientationRequest(true);
- doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+ doReturn(USER_MIN_ASPECT_RATIO_3_2)
+ .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .getUserMinAspectRatioOverrideCode();
}
private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
@@ -452,10 +470,11 @@
}
private void prepareActivityThatShouldApplyUserFullscreenOverride() {
- spyOn(mController);
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
mDisplayContent.setIgnoreOrientationRequest(true);
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+ .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
}
@@ -578,9 +597,10 @@
@Test
@EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertTrue(mController.shouldOverrideMinAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@@ -588,9 +608,10 @@
public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue()
throws Exception {
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertTrue(mController.shouldOverrideMinAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@@ -598,17 +619,19 @@
public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse()
throws Exception {
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldOverrideMinAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldOverrideMinAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@@ -618,9 +641,9 @@
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
- assertFalse(mController.shouldOverrideMinAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@@ -628,9 +651,10 @@
public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse()
throws Exception {
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
- assertFalse(mController.shouldOverrideMinAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .shouldOverrideMinAspectRatio());
}
@Test
@@ -860,8 +884,10 @@
doReturn(true).when(mDisplayContent.mAppCompatCameraPolicy)
.isTreatmentEnabledForActivity(eq(mActivity));
- assertEquals(mController.getSplitScreenAspectRatio(),
- mController.getFixedOrientationLetterboxAspectRatio(
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ assertEquals(aspectRatioOverrides.getSplitScreenAspectRatio(),
+ aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(
mActivity.getParent().getConfiguration()), /* delta */ 0.01);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
// Simulate when the window is exiting and cleanupAnimation invoked
// (e.g. screen off during RecentsAnimation animating), will expect the window receives
// onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win1.mHasSurface = true;
win1.mAnimatingExit = true;
win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
// Simulating win1 has shown IME and being IME layering/input target
mDisplayContent.setImeLayeringTarget(win1);
mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
spyOn(mDisplayContent);
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
- .prepareToShowInTransaction(any(), anyFloat());
+ mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
makeWindowVisibleAndDrawn(mImeWindow);
assertTrue(mImeWindow.isOnScreen());
assertFalse(mImeWindow.isParentWindowHidden());
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 3078df0..d29505f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -274,17 +274,28 @@
@Test
public void testAttachApplication() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+ .setCreateTask(true).build();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+ .setUseProcess(activity.app).setTask(activity.getTask()).build();
activity.detachFromProcess();
- mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ topActivity.detachFromProcess();
+ mAtm.startProcessAsync(topActivity, false /* knownToBeDead */,
true /* isTop */, "test" /* hostingType */);
+ // Even if the activity is added after topActivity, the start order should still follow
+ // z-order, i.e. the topActivity will be started first.
+ mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ false /* isTop */, "test" /* hostingType */);
+ assertEquals(2, mAtm.mStartingProcessActivities.size());
+ assertEquals("Top record must be at the tail to start first",
+ topActivity, mAtm.mStartingProcessActivities.get(1));
final WindowProcessController proc = mSystemServicesTestRule.addProcess(
activity.packageName, activity.processName,
6789 /* pid */, activity.info.applicationInfo.uid);
try {
mRootWindowContainer.attachApplication(proc);
- verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
- anyBoolean());
+ verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc),
+ anyBoolean(), anyBoolean());
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -428,7 +439,8 @@
final Rect bounds = new Rect(task.getBounds());
bounds.scale(0.5f);
task.setBounds(bounds);
- assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(task.autoRemoveRecents).isFalse();
}
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 64527cb..ae88b1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -64,6 +64,7 @@
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -103,6 +104,7 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -494,7 +496,7 @@
// Activity is sandboxed; it is in size compat mode since it is not resizable and has a
// max aspect ratio.
assertActivityMaxBoundsSandboxed();
- assertScaled();
+ assertDownScaled();
}
@Test
@@ -514,7 +516,7 @@
// The bounds should be [100, 0 - 1100, 2500].
assertEquals(origBounds.width(), currentBounds.width());
assertEquals(origBounds.height(), currentBounds.height());
- assertScaled();
+ assertDownScaled();
// The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100.
final float scale = (float) display.mBaseDisplayHeight / currentBounds.height();
@@ -554,7 +556,7 @@
assertEquals(origBounds.width(), currentBounds.width());
assertEquals(origBounds.height(), currentBounds.height());
assertEquals(offsetX, mActivity.getBounds().left);
- assertScaled();
+ assertDownScaled();
// Activity is sandboxed due to size compat mode.
assertActivityMaxBoundsSandboxed();
@@ -692,7 +694,7 @@
// The configuration bounds [820, 0 - 1820, 2500] should keep the same.
assertEquals(originalBounds.width(), currentBounds.width());
assertEquals(originalBounds.height(), currentBounds.height());
- assertScaled();
+ assertDownScaled();
// Activity max bounds are sandboxed due to size compat mode on the new display.
assertActivityMaxBoundsSandboxed();
@@ -751,7 +753,7 @@
assertEquals(origAppBounds.width(), appBounds.width());
assertEquals(origAppBounds.height(), appBounds.height());
// The activity is 1000x1400 and the display is 2500x1000.
- assertScaled();
+ assertDownScaled();
final float scale = mActivity.getCompatScale();
// The position in configuration should be in app coordinates.
final Rect screenBounds = mActivity.getBounds();
@@ -848,7 +850,7 @@
// Size compatibility mode is able to handle orientation change so the process shouldn't be
// restarted and the override configuration won't be cleared.
verify(mActivity, never()).restartProcessIfVisible();
- assertScaled();
+ assertDownScaled();
// Activity max bounds are sandboxed due to size compat mode, even if is not visible.
assertActivityMaxBoundsSandboxed();
@@ -1081,10 +1083,11 @@
// Simulate the user selecting the fullscreen user aspect ratio override
spyOn(activity.mWmService.mLetterboxConfiguration);
- spyOn(activity.mLetterboxUiController);
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
.isUserAppAspectRatioFullscreenEnabled();
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController)
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+ .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
assertFalse(activity.shouldCreateCompatDisplayInsets());
}
@@ -1102,9 +1105,10 @@
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Simulate the user selecting the fullscreen user aspect ratio override
- spyOn(activity.mLetterboxUiController);
- doReturn(true).when(activity.mLetterboxUiController)
- .isSystemOverrideToFullscreenEnabled();
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isSystemOverrideToFullscreenEnabled();
assertFalse(activity.shouldCreateCompatDisplayInsets());
}
@@ -1123,7 +1127,8 @@
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should not be sandboxed, even though it is letterboxed.
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
.isEqualTo(activity.getDisplayArea().getBounds());
}
@@ -1164,7 +1169,8 @@
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should not be sandboxed, even though it is letterboxed.
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
.isEqualTo(activity.getDisplayArea().getBounds());
}
@@ -1187,7 +1193,8 @@
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should not be sandboxed, even though it is letterboxed.
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
.isEqualTo(activity.getDisplayArea().getBounds());
}
@@ -1618,6 +1625,85 @@
activity.getBounds().width(), 0.5);
}
+
+ /**
+ * Test that a freeform unresizeable activity can be down-scaled to fill its smaller parent
+ * bounds.
+ */
+ @Test
+ public void testCompatScaling_freeformUnresizeableApp_largerThanParent_downScaled() {
+ final int dw = 600;
+ final int dh = 800;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Resize app to make original app bounds larger than parent bounds.
+ mTask.getWindowConfiguration().setAppBounds(
+ new Rect(0, 0, dw - 300, dh - 400));
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // App should enter size compat mode and be down-scaled to fill new parent bounds.
+ assertDownScaled();
+ }
+
+ /**
+ * Test that when desktop mode is enabled, a freeform unresizeable activity can be up-scaled to
+ * fill its larger parent bounds.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void testCompatScaling_freeformUnresizeableApp_smallerThanParent_upScaled() {
+ doReturn(true).when(() ->
+ DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
+ final int dw = 600;
+ final int dh = 800;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Resize app to make original app bounds smaller than parent bounds.
+ mTask.getWindowConfiguration().setAppBounds(
+ new Rect(0, 0, dw + 300, dh + 400));
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // App should enter size compat mode and be up-scaled to fill parent bounds.
+ assertUpScaled();
+ }
+
+ /**
+ * Test that when desktop mode is disabled, a freeform unresizeable activity cannot be up-scaled
+ * despite its larger parent bounds.
+ */
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void testSizeCompatScaling_freeformUnresizeableApp_smallerThanParent_notScaled() {
+ final int dw = 600;
+ final int dh = 800;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertFalse(mActivity.inSizeCompatMode());
+ final Rect originalAppBounds = mActivity.getBounds();
+
+ // Resize app to make original app bounds smaller than parent bounds.
+ mTask.getWindowConfiguration().setAppBounds(
+ new Rect(0, 0, dw + 300, dh + 400));
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // App should enter size compat mode but remain its original size.
+ assertTrue(mActivity.inSizeCompatMode());
+ assertEquals(originalAppBounds, mActivity.getBounds());
+ }
+
@Test
public void testGetLetterboxInnerBounds_noScalingApplied() {
// Set up a display in portrait and ignoring orientation request.
@@ -1636,7 +1722,8 @@
addWindowToActivity(mActivity);
// App should launch in fullscreen.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Activity inherits max bounds from TaskDisplayArea.
@@ -1650,8 +1737,9 @@
assertTrue(rotatedDisplayBounds.width() < rotatedDisplayBounds.height());
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
@@ -1762,7 +1850,8 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertActivityMaxBoundsSandboxed();
@@ -1793,7 +1882,8 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Activity bounds should respect minimum aspect ratio for activity.
@@ -1823,7 +1913,8 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Activity bounds should respect maximum aspect ratio for activity.
@@ -1853,7 +1944,8 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Activity bounds should respect aspect ratio override for fixed orientation letterbox.
@@ -1950,7 +2042,8 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over
@@ -1976,7 +2069,8 @@
// App should launch in fixed orientation letterbox.
// Activity bounds should be 700x1400 with the ratio as the display.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFitted();
assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
@@ -1986,7 +2080,7 @@
// After we rotate, the activity should go in the size-compat mode and report the same
// configuration values.
- assertScaled();
+ assertDownScaled();
assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
@@ -2018,7 +2112,8 @@
// App should launch in fixed orientation letterbox.
// Activity bounds should be 700x1400 with the ratio as the display.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFitted();
assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
@@ -2052,7 +2147,8 @@
final Rect activityBounds = new Rect(mActivity.getBounds());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
@@ -2081,9 +2177,10 @@
.isUserAppAspectRatioFullscreenEnabled();
// Set user aspect ratio override
- spyOn(mActivity.mLetterboxUiController);
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
- .getUserMinAspectRatioOverrideCode();
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+ .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .getUserMinAspectRatioOverrideCode();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2105,9 +2202,10 @@
.isUserAppAspectRatioFullscreenEnabled();
// Set user aspect ratio override
- spyOn(mActivity.mLetterboxUiController);
- doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
- .getUserMinAspectRatioOverrideCode();
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
+ .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .getUserMinAspectRatioOverrideCode();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2124,9 +2222,10 @@
final int displayHeight = 1400;
setUpDisplaySizeWithApp(displayWidth, displayHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .isSystemOverrideToFullscreenEnabled();
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isSystemOverrideToFullscreenEnabled();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2143,9 +2242,10 @@
final int displayHeight = 1600;
setUpDisplaySizeWithApp(displayWidth, displayHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .isSystemOverrideToFullscreenEnabled();
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isSystemOverrideToFullscreenEnabled();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2588,7 +2688,8 @@
final Rect activityBounds = new Rect(mActivity.getBounds());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
@@ -2632,13 +2733,14 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+ DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2667,13 +2769,14 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+ DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2693,13 +2796,14 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+ DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2719,13 +2823,14 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
// App should launch in fixed orientation letterbox.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
// Checking that there is no size compat mode.
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
+ DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2748,8 +2853,9 @@
assertTrue(displayBounds.width() < displayBounds.height());
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertEquals(activityBounds.width(), newActivityBounds.width());
assertEquals(activityBounds.height(), newActivityBounds.height());
assertActivityMaxBoundsSandboxed();
@@ -2765,7 +2871,8 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
// App should launch in fullscreen.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Activity inherits max bounds from TaskDisplayArea.
assertMaxBoundsInheritDisplayAreaBounds();
@@ -2778,8 +2885,9 @@
assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height());
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
@@ -2799,7 +2907,8 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Launch another portrait fixed app.
@@ -2821,7 +2930,8 @@
// Task and display bounds should be equal while activity should be letterboxed and
// has 700x1400 bounds with the ratio as the display.
- assertTrue(newActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(newActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(newActivity.inSizeCompatMode());
// Activity max bounds are sandboxed due to size compat mode.
assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds())
@@ -2842,7 +2952,8 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
@@ -2863,7 +2974,8 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Launch another portrait fixed app with max aspect ratio as 1.3.
@@ -2893,7 +3005,8 @@
assertActivityMaxBoundsSandboxed();
// Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(newActivity.inSizeCompatMode());
assertEquals(displayBounds.height(), newActivityBounds.height());
assertEquals((long) Math.rint(newActivityBounds.height()
@@ -2912,15 +3025,17 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
clearInvocations(mActivity);
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Rotate display to portrait.
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertThat(mActivity.inSizeCompatMode()).isTrue();
// Activity max bounds are sandboxed due to size compat mode.
assertActivityMaxBoundsSandboxed();
@@ -2930,8 +3045,9 @@
// App still in size compat, and the bounds don't change.
verify(mActivity, never()).clearSizeCompatMode();
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertEquals(activityBounds, mActivity.getBounds());
// Activity max bounds are sandboxed due to size compat.
assertActivityMaxBoundsSandboxed();
@@ -2948,7 +3064,8 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
// In fixed orientation letterbox
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertActivityMaxBoundsSandboxed();
@@ -2956,15 +3073,17 @@
rotateDisplay(display, ROTATION_90);
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertActivityMaxBoundsSandboxed();
// Rotate display to landscape.
rotateDisplay(display, ROTATION_180);
// In activity letterbox
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertActivityMaxBoundsSandboxed();
}
@@ -2982,7 +3101,8 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);
// In fixed orientation letterbox
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertActivityMaxBoundsSandboxed();
@@ -2990,15 +3110,17 @@
rotateDisplay(display, ROTATION_90);
// App should be in size compat.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertScaled();
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertDownScaled();
assertActivityMaxBoundsSandboxed();
// Rotate display to portrait.
rotateDisplay(display, ROTATION_180);
// In fixed orientation letterbox
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertActivityMaxBoundsSandboxed();
}
@@ -3182,7 +3304,7 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Non-resizable activity in size compat mode
- assertScaled();
+ assertDownScaled();
final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
assertEquals(originalBounds.width(), newBounds.width());
assertEquals(originalBounds.height(), newBounds.height());
@@ -3195,7 +3317,8 @@
assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
assertFitted();
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertActivityMaxBoundsSandboxed();
// Letterbox should fill the gap between the split screen and the letterboxed activity.
@@ -3221,7 +3344,8 @@
// Resizable activity is not in size compat mode but in the letterbox for fixed orientation.
assertFitted();
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
@@ -3244,7 +3368,7 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Non-resizable activity in size compat mode
- assertScaled();
+ assertDownScaled();
final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
assertEquals(originalBounds.width(), newBounds.width());
assertEquals(originalBounds.height(), newBounds.height());
@@ -3257,7 +3381,8 @@
assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
assertEquals(ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
assertFitted();
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertActivityMaxBoundsSandboxed();
// Activity bounds fill split screen.
@@ -3284,7 +3409,7 @@
organizer.mPrimary.setBounds(0, 0, 1000, 800);
// Non-resizable activity should be in size compat mode.
- assertScaled();
+ assertDownScaled();
assertEquals(mActivity.getBounds(), new Rect(60, 0, 940, 800));
recomputeNaturalConfigurationOfUnresizableActivity();
@@ -3861,7 +3986,7 @@
// Force activity to scaled down for size compat mode.
resizeDisplay(mTask.mDisplayContent, 700, 1400);
assertTrue(mActivity.inSizeCompatMode());
- assertScaled();
+ assertDownScaled();
assertEquals(sizeCompatScaled, mActivity.getBounds());
}
@@ -3892,7 +4017,8 @@
// orientation is not respected with insets as insets have been decoupled.
final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
final Rect displayBounds = display.getBounds();
- assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertNotNull(appBounds);
assertEquals(displayBounds.width(), appBounds.width());
assertEquals(displayBounds.height(), appBounds.height());
@@ -3923,7 +4049,8 @@
final Rect bounds = activity.getBounds();
// Activity should be letterboxed and should have portrait app bounds
- assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(bounds.height() > bounds.width());
}
@@ -3957,7 +4084,8 @@
assertNotNull(activity.getCompatDisplayInsets());
// Activity is not letterboxed for fixed orientation because orientation is respected
// with insets, and should not be in size compat mode
- assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(activity.inSizeCompatMode());
}
@@ -4204,9 +4332,10 @@
Configuration parentConfig = mActivity.getParent().getConfiguration();
- float actual = mActivity.mLetterboxUiController
- .getFixedOrientationLetterboxAspectRatio(parentConfig);
- float expected = mActivity.mLetterboxUiController.getSplitScreenAspectRatio();
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ float actual = aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(parentConfig);
+ float expected = aspectRatioOverrides.getSplitScreenAspectRatio();
assertEquals(expected, actual, DELTA_ASPECT_RATIO_TOLERANCE);
}
@@ -4357,7 +4486,7 @@
resizeDisplay(mTask.mDisplayContent, 1400, 700);
assertTrue(mActivity.inSizeCompatMode());
- assertScaled();
+ assertDownScaled();
assertEquals(sizeCompatScaled, mActivity.getBounds());
}
@@ -4380,7 +4509,8 @@
verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
@@ -4396,7 +4526,8 @@
// ActivityRecord#resolveSizeCompatModeConfiguration because mCompatDisplayInsets aren't
// null but activity doesn't enter size compat mode. Checking that areBoundsLetterboxed()
// still returns true because of the aspect ratio restrictions.
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
verifyLogAppCompatState(mActivity,
@@ -4423,7 +4554,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
verifyLogAppCompatState(mActivity,
@@ -4441,7 +4573,8 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
verifyLogAppCompatState(mActivity,
@@ -4502,7 +4635,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
assertFalse(mActivity.isEligibleForLetterboxEducation());
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
@@ -4560,7 +4694,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
assertTrue(mActivity.isEligibleForLetterboxEducation());
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
@@ -4574,7 +4709,8 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
assertTrue(mActivity.isEligibleForLetterboxEducation());
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
}
@@ -4616,7 +4752,7 @@
// Target min aspect ratio must be larger than parent aspect ratio to be applied.
final float targetMinAspectRatio = 3.0f;
- // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+ // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
.setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.setMinAspectRatio(targetMinAspectRatio).build();
@@ -4630,7 +4766,7 @@
final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
.windowConfiguration.getAppBounds());
- // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+ // Create unresizeable fixed portrait activity with min aspect ratio greater than parent
// aspect ratio.
final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
.setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
@@ -4642,12 +4778,12 @@
.windowConfiguration.getAppBounds());
// Check that aspect ratio of app bounds is equal to the min aspect ratio.
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
- assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio,
+ computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio,
+ computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
+ assertEquals(targetMinAspectRatio,
+ computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -4663,7 +4799,7 @@
// Activity should enter size compat with old density after display density change.
display.setForcedDensity(newDensity, UserHandle.USER_CURRENT);
- assertScaled();
+ assertDownScaled();
assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
// Activity should exit size compat with new density.
@@ -4902,14 +5038,25 @@
}
}
- private void assertScaled() {
- assertScaled(mActivity);
+ private void assertUpScaled() {
+ assertScaled(mActivity, /* upScalingExpected */ true);
}
- /** Asserts that the size of activity is larger than its parent so it is scaling. */
- private void assertScaled(ActivityRecord activity) {
+ private void assertDownScaled() {
+ assertScaled(mActivity, /* upScalingExpected */ false);
+ }
+
+ /**
+ * Asserts that the size of an activity differs from its parent and so it is scaling (either up
+ * or down).
+ */
+ private void assertScaled(ActivityRecord activity, boolean upScalingExpected) {
assertTrue(activity.inSizeCompatMode());
- assertNotEquals(1f, activity.getCompatScale(), 0.0001f /* delta */);
+ if (upScalingExpected) {
+ assertTrue(activity.getCompatScale() > 1f);
+ } else {
+ assertTrue(activity.getCompatScale() < 1f);
+ }
}
private void assertFitted() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a71b81e..d013053 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -90,7 +90,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
@@ -140,7 +139,6 @@
private IBinder mFragmentToken;
private WindowContainerTransaction mTransaction;
private WindowContainerToken mFragmentWindowToken;
- private RemoteAnimationDefinition mDefinition;
private IBinder mErrorToken;
private Rect mTaskFragBounds;
@@ -169,7 +167,6 @@
mTransaction = new WindowContainerTransaction();
mTransaction.setTaskFragmentOrganizer(mIOrganizer);
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
- mDefinition = new RemoteAnimationDefinition();
mErrorToken = new Binder();
final Rect displayBounds = mDisplayContent.getBounds();
mTaskFragBounds = new Rect(displayBounds.left, displayBounds.top, displayBounds.centerX(),
@@ -579,17 +576,6 @@
}
@Test
- public void testRegisterRemoteAnimations() {
- mController.registerRemoteAnimations(mIOrganizer, mDefinition);
-
- assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
-
- mController.unregisterRemoteAnimations(mIOrganizer);
-
- assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
- }
-
- @Test
public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 35b6b70..47d34a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -759,20 +759,24 @@
// Assert fixed orientation request is ignored for activity in ActivityEmbedding split.
activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
activity1.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
// Also verify the behavior on device that ignore orientation request.
mDisplayContent.setIgnoreOrientationRequest(true);
task.onConfigurationChanged(task.getParent().getConfiguration());
- assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
- assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
tf0.setResumedActivity(activity0, "test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 4bc87b1..76690ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -613,11 +613,12 @@
final ActivityRecord root = task.getTopNonFinishingActivity();
spyOn(mWm.mLetterboxConfiguration);
spyOn(root);
- spyOn(root.mLetterboxUiController);
+ spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides());
doReturn(true).when(root).fillsParent();
- doReturn(true).when(root.mLetterboxUiController)
- .shouldEnableUserAspectRatioSettings();
+ doReturn(true).when(
+ root.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .shouldEnableUserAspectRatioSettings();
doReturn(false).when(root).inSizeCompatMode();
doReturn(task).when(root).getOrganizedTask();
@@ -626,12 +627,13 @@
.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
// When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
- doReturn(false).when(root.mLetterboxUiController)
- .shouldEnableUserAspectRatioSettings();
+ doReturn(false).when(
+ root.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .shouldEnableUserAspectRatioSettings();
assertFalse(task.getTaskInfo()
.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
- doReturn(true).when(root.mLetterboxUiController)
- .shouldEnableUserAspectRatioSettings();
+ doReturn(true).when(root.mAppCompatController
+ .getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings();
// When in size compat mode the button is not enabled
doReturn(true).when(root).inSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 00a8842..38ad9a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -27,6 +27,7 @@
import android.os.PowerManager.WakeReason;
import android.util.proto.ProtoOutputStream;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -35,6 +36,7 @@
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.util.Collections;
class TestWindowManagerPolicy implements WindowManagerPolicy {
@@ -362,4 +364,9 @@
public boolean isGlobalKey(int keyCode) {
return false;
}
+
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return new KeyboardShortcutGroup("", Collections.emptyList());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
}
private static void makeWallpaperWindowShown(WindowState w) {
- final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
- w.mWinAnimator.mSurfaceController = windowSurfaceController;
w.mWinAnimator.mLastAlpha = 1;
- when(windowSurfaceController.getShown()).thenReturn(true);
+ spyOn(w.mWinAnimator);
+ doReturn(true).when(w.mWinAnimator).getShown();
}
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
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 a94b586..401964c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -77,6 +78,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -91,6 +93,8 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -801,18 +805,11 @@
final TestWindowContainer root2 = builder.setLayer(0).build();
+ assertEquals("Roots have the same z-order", 0, root.compareTo(root2));
assertEquals(0, root.compareTo(root));
assertEquals(-1, child1.compareTo(child2));
assertEquals(1, child2.compareTo(child1));
- boolean inTheSameTree = true;
- try {
- root.compareTo(root2);
- } catch (IllegalArgumentException e) {
- inTheSameTree = false;
- }
- assertFalse(inTheSameTree);
-
assertEquals(-1, child1.compareTo(child11));
assertEquals(1, child21.compareTo(root));
assertEquals(1, child21.compareTo(child12));
@@ -960,6 +957,25 @@
assertTrue(child.handlesOrientationChangeFromDescendant(orientation));
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void testAddLocalInsets_addsFlagsFromProvider() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ final Binder owner = new Binder();
+ Rect insetsRect = new Rect(0, 200, 1080, 700);
+ final int flags = FLAG_FORCE_CONSUMING;
+ final InsetsFrameProvider provider =
+ new InsetsFrameProvider(owner, 1, WindowInsets.Type.captionBar())
+ .setArbitraryRectangle(insetsRect)
+ .setFlags(flags);
+ task.addLocalInsetsFrameProvider(provider, owner);
+
+ final int sourceFlags = task.mLocalInsetsSources.get(provider.getId()).getFlags();
+ assertEquals(flags, sourceFlags);
+ }
+
private static void addLocalInsets(WindowContainer wc) {
final Binder owner = new Binder();
Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
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 fcf7a3f..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
spyOn(session);
assertTrue(session.mCanAddInternalSystemWindow);
- final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
- session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
- LayoutParams.TYPE_PHONE);
+ final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+ session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
verify(session).setHasOverlayUi(true);
- session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
- LayoutParams.TYPE_PHONE);
+ session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
verify(session).setHasOverlayUi(false);
}
@Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
spyOn(win.mTransitionController);
doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
+ assertNull(win.mWinAnimator.mSurfaceControl);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
@@ -1294,8 +1290,6 @@
@Test
public void testRelayout_appWindowSendActivityWindowInfo() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
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 d3504cc..fb81a52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
@@ -75,10 +76,12 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.Rational;
import android.view.Display;
+import android.view.InsetsSource;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.ITaskFragmentOrganizer;
@@ -904,7 +907,8 @@
0 /* index */,
WindowInsets.Type.systemOverlays(),
new Rect(0, 0, 1080, 200),
- null /* boundingRects */);
+ null /* boundingRects */,
+ 0 /* flags */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -930,7 +934,8 @@
0 /* index */,
WindowInsets.Type.systemOverlays(),
new Rect(0, 0, 1080, 200),
- boundingRects);
+ boundingRects,
+ 0 /* flags */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertArrayEquals(boundingRects, navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -938,6 +943,30 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void testAddInsetsSource_withFlags() {
+ final Task rootTask = createTask(mDisplayContent);
+
+ final Task insetsReceiverTask = createTaskInRootTask(rootTask, 0);
+ insetsReceiverTask.getConfiguration().windowConfiguration
+ .setBounds(new Rect(0, 200, 1080, 700));
+
+ final @InsetsSource.Flags int flags = FLAG_FORCE_CONSUMING;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.addInsetsSource(
+ insetsReceiverTask.mRemoteToken.toWindowContainerToken(),
+ new Binder(),
+ 0 /* index */,
+ WindowInsets.Type.systemOverlays(),
+ new Rect(0, 0, 1080, 200),
+ null /* boundingRects */,
+ flags);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertEquals(flags, insetsReceiverTask.mLocalInsetsSources.valueAt(0).getFlags());
+ }
+
+ @Test
public void testRemoveInsetsSource() {
final Task rootTask = createTask(mDisplayContent);
@@ -952,7 +981,8 @@
0 /* index */,
WindowInsets.Type.systemOverlays(),
new Rect(0, 0, 1080, 200),
- null /* boundingRects */);
+ null /* boundingRects */,
+ 0 /* flags */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
final WindowContainerTransaction wct2 = new WindowContainerTransaction();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e6648da..0cb22ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -86,6 +87,7 @@
ApplicationInfo info = mock(ApplicationInfo.class);
info.packageName = "test.package.name";
+ doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
mWpc = new WindowProcessController(
mAtm, info, null, 0, -1, null, mMockListener);
mWpc.setThread(mock(IApplicationThread.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
similarity index 97%
rename from services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
rename to services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index c183403..48a8d55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -63,7 +63,7 @@
*/
@SmallTest
@Presubmit
-public class WindowTracingTest {
+public class WindowTracingLegacyTest {
private static final byte[] MAGIC_HEADER = new byte[]{
0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
@@ -88,7 +88,7 @@
mFile = testContext.getFileStreamPath("tracing_test.dat");
mFile.delete();
- mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
+ mWindowTracing = new WindowTracingLegacy(mFile, mWmMock, mChoreographer,
new WindowManagerGlobalLock(), 1024);
}
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index 49e9232..14c9ea5 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -159,7 +159,7 @@
@Override
public String toString() {
String bluetoothDeviceList = supportedBluetoothDevices.stream()
- .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+ .map(BluetoothDevice::toString).collect(Collectors.joining(", "));
return String.format(Locale.US,
"[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b518c60d..aebae4e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -50,7 +50,6 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -2636,9 +2635,9 @@
if (resultCode == SATELLITE_RESULT_SUCCESS) {
if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
List<ProvisionSubscriberId> list =
- Collections.singletonList(resultData.getParcelable(
+ resultData.getParcelableArrayList(
KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
- ProvisionSubscriberId.class));
+ ProvisionSubscriberId.class);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onResult(list)));
} else {
@@ -2692,13 +2691,13 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SATELLITE_RESULT_SUCCESS) {
- if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+ if (resultData.containsKey(KEY_IS_SATELLITE_PROVISIONED)) {
boolean isIsProvisioned =
- resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+ resultData.getBoolean(KEY_IS_SATELLITE_PROVISIONED);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onResult(isIsProvisioned)));
} else {
- loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+ loge("KEY_IS_SATELLITE_PROVISIONED does not exist.");
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onError(new SatelliteException(
SATELLITE_RESULT_REQUEST_FAILED))));
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7be52ea..3dbda7a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3411,7 +3411,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestProvisionSubscriberIds(in ResultReceiver receiver);
+ void requestProvisionSubscriberIds(in ResultReceiver result);
/**
* Request to get provisioned status for given a satellite subscriber id.
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index ccc3683..78d93e1 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -34,6 +34,11 @@
srcs: ["src/**/Close*"],
}
+filegroup {
+ name: "FlickerTestsIme2-src",
+ srcs: ["src/**/ShowImeOnAppStart*"],
+}
+
android_test {
name: "FlickerTestsIme",
defaults: ["FlickerTestsDefault"],
@@ -77,9 +82,23 @@
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
+ srcs: [":FlickerTestsIme2-src"],
+ static_libs: [
+ "FlickerTestsBase",
+ "FlickerTestsImeCommon",
+ ],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "FlickerTestsIme3",
+ defaults: ["FlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
exclude_srcs: [
":FlickerTestsIme1-src",
+ ":FlickerTestsIme2-src",
":FlickerTestsImeCommon-src",
],
static_libs: [
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9a5e88b..5121f667 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -119,7 +119,7 @@
): UiObject2? {
if (
wmHelper.getWindow(innerHelper)?.windowingMode !=
- WindowingMode.WINDOWING_MODE_FREEFORM.value
+ WindowingMode.WINDOWING_MODE_FREEFORM.value
)
error("expected a freeform window with caption but window is not in freeform mode")
val captions =
@@ -147,7 +147,17 @@
val endX = startX + horizontalChange
// drag the specified corner of the window to the end coordinate.
- device.drag(startX, startY, endX, endY, 100)
+ dragWindow(startX, startY, endX, endY, wmHelper, device)
+ }
+
+ /** Drag a window from a source coordinate to a destination coordinate. */
+ fun dragWindow(
+ startX: Int, startY: Int,
+ endX: Int, endY: Int,
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice
+ ) {
+ device.drag(startX, startY, endX, endY, /* steps= */ 100)
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
@@ -165,4 +175,37 @@
Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
}
}
+
+ /** Exit desktop mode by dragging the app handle to the top drag zone. */
+ fun exitDesktopWithDragToTopDragZone(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ ) {
+ dragAppWindowToTopDragZone(wmHelper, device)
+ waitForTransitionToFullscreen(wmHelper)
+ }
+
+ private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
+ val startX = windowRect.centerX()
+ val endX = displayRect.centerX()
+ val startY = windowRect.top
+ val endY = 0 // top of the screen
+
+ // drag the app window to top drag zone
+ device.drag(startX, startY, endX, endY, 100)
+ }
+
+ /** Wait for transition to full screen to finish. */
+ private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(innerHelper)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
}
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
index 8db3705..4a99bd4 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -31,7 +31,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="input_.*" />
<!-- Pull files created by tests, like the output of screenshot tests -->
- <option name="directory-keys" value="/storage/emulated/0/InputTests" />
+ <option name="directory-keys" value="/sdcard/Download/InputTests" />
<option name="collect-on-run-ended-only" value="false" />
</metrics_collector>
</configuration>
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index e0f8c6d..abfe549 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
-import android.os.Environment
import android.view.ContextThemeWrapper
import android.view.PointerIcon
import android.view.flags.Flags.enableVectorCursorA11ySettings
@@ -137,11 +136,20 @@
assumeTrue(enableVectorCursors())
assumeTrue(enableVectorCursorA11ySettings())
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+ /* force= */ true)
+ theme.applyStyle(
+ PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE),
+ /* force= */ true)
val pointerScale = 2f
val pointerIcon =
PointerIcon.getLoadedSystemIcon(
- context,
+ ContextThemeWrapper(context, theme),
PointerIcon.TYPE_ARROW,
/* useLargeIcons= */ false,
pointerScale)
@@ -158,8 +166,7 @@
const val SCREEN_WIDTH_DP = 480
const val SCREEN_HEIGHT_DP = 800
const val ASSETS_PATH = "tests/input/assets"
- val TEST_OUTPUT_PATH = Environment.getExternalStorageDirectory().absolutePath +
- "/InputTests/" +
- PointerIconLoadingTest::class.java.simpleName
+ val TEST_OUTPUT_PATH =
+ "/sdcard/Download/InputTests/" + PointerIconLoadingTest::class.java.simpleName
}
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..2539653
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LegacyProtoLogViewerConfigReaderTest {
+ private static final String TEST_VIEWER_CONFIG = "{\n"
+ + " \"version\": \"1.0.0\",\n"
+ + " \"messages\": {\n"
+ + " \"70933285\": {\n"
+ + " \"message\": \"Test completed successfully: %b\",\n"
+ + " \"level\": \"ERROR\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1792430067\": {\n"
+ + " \"message\": \"Attempted to add window to a display that does not exist: %d."
+ + " Aborting.\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1352021864\": {\n"
+ + " \"message\": \"Test 2\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"409412266\": {\n"
+ + " \"message\": \"Window %s is already added\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"groups\": {\n"
+ + " \"GENERIC_WM\": {\n"
+ + " \"tag\": \"WindowManager\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+
+ private LegacyProtoLogViewerConfigReader
+ mConfig = new LegacyProtoLogViewerConfigReader();
+ private File mTestViewerConfig;
+
+ @Before
+ public void setUp() throws IOException {
+ mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+ writer.write(TEST_VIEWER_CONFIG);
+ writer.close();
+ }
+
+ @After
+ public void tearDown() {
+ //noinspection ResultOfMethodCallIgnored
+ mTestViewerConfig.delete();
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
+ assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+ assertEquals("Test 2", mConfig.getViewerString(1352021864));
+ assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig_invalidFile() {
+ mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
+ // No exception is thrown.
+ assertNull(mConfig.getViewerString(1));
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index dbd85d3..be0e8bc 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -20,75 +20,77 @@
import static org.junit.Assert.assertNull;
import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.zip.GZIPOutputStream;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
-@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class ProtoLogViewerConfigReaderTest {
- private static final String TEST_VIEWER_CONFIG = "{\n"
- + " \"version\": \"1.0.0\",\n"
- + " \"messages\": {\n"
- + " \"70933285\": {\n"
- + " \"message\": \"Test completed successfully: %b\",\n"
- + " \"level\": \"ERROR\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1792430067\": {\n"
- + " \"message\": \"Attempted to add window to a display that does not exist: %d."
- + " Aborting.\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1352021864\": {\n"
- + " \"message\": \"Test 2\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"409412266\": {\n"
- + " \"message\": \"Window %s is already added\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " }\n"
- + " },\n"
- + " \"groups\": {\n"
- + " \"GENERIC_WM\": {\n"
- + " \"tag\": \"WindowManager\"\n"
- + " }\n"
- + " }\n"
- + "}\n";
+ private static final String TEST_GROUP_NAME = "MY_TEST_GROUP";
+ private static final String TEST_GROUP_TAG = "TEST";
+ private static final String OTHER_TEST_GROUP_NAME = "MY_OTHER_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP_TAG = "OTHER_TEST";
- private LegacyProtoLogViewerConfigReader
- mConfig = new LegacyProtoLogViewerConfigReader();
- private File mTestViewerConfig;
+ private static final byte[] TEST_VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP_NAME)
+ .setTag(TEST_GROUP_TAG)
+ ).addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(OTHER_TEST_GROUP_NAME)
+ .setTag(OTHER_TEST_GROUP_TAG)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Log Message 1 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Log Message 2 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Log Message 3 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Log Message 4 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(2)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test Log Message 5 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(2)
+ ).build().toByteArray();
+
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
+ () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+
+ private ProtoLogViewerConfigReader mConfig;
@Before
- public void setUp() throws IOException {
- mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
- OutputStreamWriter writer = new OutputStreamWriter(
- new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
- writer.write(TEST_VIEWER_CONFIG);
- writer.close();
- }
-
- @After
- public void tearDown() {
- //noinspection ResultOfMethodCallIgnored
- mTestViewerConfig.delete();
+ public void before() {
+ mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider);
}
@Test
@@ -98,17 +100,26 @@
@Test
public void loadViewerConfig() {
- mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
- assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
- assertEquals("Test 2", mConfig.getViewerString(1352021864));
- assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
- assertNull(mConfig.getViewerString(1));
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertEquals("My Test Log Message 1 %b", mConfig.getViewerString(1));
+ assertEquals("My Test Log Message 2 %b", mConfig.getViewerString(2));
+ assertEquals("My Test Log Message 3 %b", mConfig.getViewerString(3));
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
@Test
- public void loadViewerConfig_invalidFile() {
- mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
- // No exception is thrown.
+ public void unloadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME, OTHER_TEST_GROUP_NAME });
+ mConfig.unloadViewerConfig(new String[] { TEST_GROUP_NAME });
assertNull(mConfig.getViewerString(1));
+ assertNull(mConfig.getViewerString(2));
+ assertNull(mConfig.getViewerString(3));
+ assertEquals("My Test Log Message 4 %b", mConfig.getViewerString(4));
+ assertEquals("My Test Log Message 5 %b", mConfig.getViewerString(5));
+
+ mConfig.unloadViewerConfig(new String[] { OTHER_TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
}
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
index 2c9361d..f9e004b 100644
--- a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -17,6 +17,7 @@
import android.app.trust.TrustManager
import android.content.Context
+import android.security.Flags.shouldTrustManagerListenForPrimaryAuth
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
import android.trust.test.lib.LockStateTrackingRule
@@ -154,13 +155,17 @@
private fun triggerSuccessfulUnlock() {
screenLockRule.successfulScreenLockAttempt()
- trustAgentRule.reportSuccessfulUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportSuccessfulUnlock()
+ }
await()
}
private fun triggerFailedUnlock() {
screenLockRule.failedScreenLockAttempt()
- trustAgentRule.reportFailedUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportFailedUnlock()
+ }
await()
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index 0db0d95..5f9a710 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -23,10 +23,15 @@
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.launchActivityAsUserSync;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.sendBundleAndWaitForReply;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_EDITTEXT_CENTER;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_IME_SHOWN;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_EDITTEXT_POSITION;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_HIDE_IME;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_SHOW_IME;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -49,6 +54,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -96,7 +102,7 @@
}
@Test
- public void driverShowImeNotAffectPassenger() {
+ public void driverShowImeNotAffectPassenger() throws Exception {
assertDriverImeHidden();
assertPassengerImeHidden();
@@ -105,6 +111,34 @@
}
@Test
+ @Ignore("b/352823913")
+ public void passengerShowImeNotAffectDriver() throws Exception {
+ assertDriverImeHidden();
+ assertPassengerImeHidden();
+
+ showPassengerImeAndAssert();
+ assertDriverImeHidden();
+ }
+
+ @Test
+ public void driverHideImeNotAffectPassenger() throws Exception {
+ showDriverImeAndAssert();
+ showPassengerImeAndAssert();
+
+ hideDriverImeAndAssert();
+ assertPassengerImeShown();
+ }
+
+ @Test
+ public void passengerHideImeNotAffectDriver() throws Exception {
+ showDriverImeAndAssert();
+ showPassengerImeAndAssert();
+
+ hidePassengerImeAndAssert();
+ assertDriverImeShown();
+ }
+
+ @Test
public void imeListNotEmpty() {
List<InputMethodInfo> driverImeList = mInputMethodManager.getInputMethodList();
assertWithMessage("Driver IME list shouldn't be empty")
@@ -156,6 +190,11 @@
setImeForUser(passenger, driver);
}
+ private void assertDriverImeShown() {
+ assertWithMessage("Driver IME should be shown")
+ .that(mActivity.isMyImeVisible()).isTrue();
+ }
+
private void assertDriverImeHidden() {
assertWithMessage("Driver IME should be hidden")
.that(mActivity.isMyImeVisible()).isFalse();
@@ -167,13 +206,75 @@
Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
mPeerUserId, bundleToSend);
assertWithMessage("Passenger IME should be hidden")
- .that(receivedBundle.getInt(KEY_RESULT_CODE)).isEqualTo(REPLY_IME_HIDDEN);
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN, /* defaultValue= */ true)).isFalse();
}
- private void showDriverImeAndAssert() {
+ private void assertPassengerImeShown() {
+ final Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_IME_STATUS);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ assertWithMessage("Passenger IME should be shown")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN)).isTrue();
+ }
+
+ private void showDriverImeAndAssert() throws Exception {
+ // WindowManagerInternal only allows the top focused display to show IME, so this method
+ // taps the driver display in case it is not the top focused display.
+ moveDriverDisplayToTop();
+
mActivity.showMyImeAndWait();
}
+ private void hideDriverImeAndAssert() {
+ mActivity.hideMyImeAndWait();
+ }
+
+ private void showPassengerImeAndAssert() throws Exception {
+ // WindowManagerInternal only allows the top focused display to show IME, so this method
+ // taps the passenger display in case it is not the top focused display.
+ movePassengerDisplayToTop();
+
+ Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_SHOW_IME);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+
+ assertWithMessage("Passenger IME should be shown")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN)).isTrue();
+ }
+
+ private void hidePassengerImeAndAssert() {
+ Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_HIDE_IME);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+
+ assertWithMessage("Passenger IME should be hidden")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN, /* defaultValue= */ true)).isFalse();
+ }
+
+ private void moveDriverDisplayToTop() throws Exception {
+ float[] driverEditTextCenter = mActivity.getEditTextCenter();
+ SystemUtil.runShellCommand(mUiAutomation, String.format("input tap %f %f",
+ driverEditTextCenter[0], driverEditTextCenter[1]));
+ }
+
+ private void movePassengerDisplayToTop() throws Exception {
+ final Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_EDITTEXT_POSITION);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ final float[] passengerEditTextCenter = receivedBundle.getFloatArray(KEY_EDITTEXT_CENTER);
+
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_DISPLAY_ID);
+ receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ final int passengerDisplayId = receivedBundle.getInt(KEY_DISPLAY_ID);
+ SystemUtil.runShellCommand(mUiAutomation, String.format("input -d %d tap %f %f",
+ passengerDisplayId, passengerEditTextCenter[0], passengerEditTextCenter[1]));
+ }
+
/**
* Disables/enables IME for {@code user1}, then verifies that the IME settings for {@code user1}
* has changed as expected and {@code user2} stays the same.
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index 3c97b79..fa0aa19 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -16,20 +16,25 @@
package com.android.server.inputmethod.multisessiontest;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_EDITTEXT_CENTER;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_IME_SHOWN;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_SHOWN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_EDITTEXT_POSITION;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_HIDE_IME;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_SHOW_IME;
import android.app.Activity;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
-import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import androidx.annotation.WorkerThread;
import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import com.android.compatibility.common.util.PollingCheck;
@@ -43,7 +48,6 @@
private static final long WAIT_IME_TIMEOUT_MS = 3000;
private EditText mEditor;
- private InputMethodManager mImm;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -52,19 +56,56 @@
+ Process.myUserHandle().getIdentifier() + " on display "
+ getDisplay().getDisplayId());
setContentView(R.layout.main_activity);
- mImm = getSystemService(InputMethodManager.class);
mEditor = requireViewById(R.id.edit_text);
}
@Override
+ protected void onResume() {
+ super.onResume();
+ Log.v(TAG, "onResume");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Log.v(TAG, "onPause");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.v(TAG, "onResume");
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.v(TAG, "onWindowFocusChanged " + hasFocus);
+ }
+
+ @Override
+ @WorkerThread
protected Bundle onBundleReceived(Bundle receivedBundle) {
final int requestCode = receivedBundle.getInt(KEY_REQUEST_CODE);
Log.v(TAG, "onBundleReceived() with request code:" + requestCode);
final Bundle replyBundle = new Bundle();
switch (requestCode) {
case REQUEST_IME_STATUS:
- replyBundle.putInt(KEY_RESULT_CODE,
- isMyImeVisible() ? REPLY_IME_SHOWN : REPLY_IME_HIDDEN);
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_SHOW_IME:
+ showMyImeAndWait();
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_HIDE_IME:
+ hideMyImeAndWait();
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_EDITTEXT_POSITION:
+ replyBundle.putFloatArray(KEY_EDITTEXT_CENTER, getEditTextCenter());
+ break;
+ case REQUEST_DISPLAY_ID:
+ replyBundle.putInt(KEY_DISPLAY_ID, getDisplay().getDisplayId());
break;
default:
throw new RuntimeException("Received undefined request code:" + requestCode);
@@ -77,23 +118,41 @@
return insets == null ? false : insets.isVisible(WindowInsetsCompat.Type.ime());
}
+ float[] getEditTextCenter() {
+ final float editTextCenterX = mEditor.getX() + 0.5f * mEditor.getWidth();
+ final float editTextCenterY = mEditor.getY() + 0.5f * mEditor.getHeight();
+ return new float[]{editTextCenterX, editTextCenterY};
+ }
+
+ @WorkerThread
void showMyImeAndWait() {
- Log.v(TAG, "showSoftInput");
runOnUiThread(() -> {
- // requestFocus() must run on UI thread.
+ // View#requestFocus() and WindowInsetsControllerCompat#show() must run on UI thread.
if (!mEditor.requestFocus()) {
Log.e(TAG, "Failed to focus on mEditor");
return;
}
- if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
- Log.e(TAG, String.format("Failed to show my IME as user %d, "
- + "mEditor:focused=%b,hasWindowFocus=%b",
- Process.myUserHandle().getIdentifier(),
- mEditor.isFocused(), mEditor.hasWindowFocus()));
- }
+ // Compared to mImm.showSoftInput(), the call below is the recommended way to show the
+ // keyboard because it is guaranteed to be scheduled after the window is focused.
+ Log.v(TAG, "showSoftInput");
+ WindowCompat.getInsetsController(getWindow(), mEditor).show(
+ WindowInsetsCompat.Type.ime());
});
PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
- String.format("My IME (user %d) didn't show up",
+ String.format("%s: My IME (user %d) didn't show up", TAG,
+ Process.myUserHandle().getIdentifier()));
+ }
+
+ @WorkerThread
+ void hideMyImeAndWait() {
+ runOnUiThread(() -> {
+ Log.v(TAG, "hideSoftInput");
+ // WindowInsetsControllerCompat#hide() must run on UI thread.
+ WindowCompat.getInsetsController(getWindow(), mEditor)
+ .hide(WindowInsetsCompat.Type.ime());
+ });
+ PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> !isMyImeVisible(),
+ String.format("%s: My IME (user %d) is still shown", TAG,
Process.myUserHandle().getIdentifier()));
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
index 1501bfb..68c9d54 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
@@ -21,9 +21,13 @@
}
public static final String KEY_REQUEST_CODE = "key_request_code";
- public static final String KEY_RESULT_CODE = "key_result_code";
+ public static final String KEY_EDITTEXT_CENTER = "key_edittext_center";
+ public static final String KEY_DISPLAY_ID = "key_display_id";
+ public static final String KEY_IME_SHOWN = "key_ime_shown";
public static final int REQUEST_IME_STATUS = 1;
- public static final int REPLY_IME_SHOWN = 2;
- public static final int REPLY_IME_HIDDEN = 3;
+ public static final int REQUEST_SHOW_IME = 2;
+ public static final int REQUEST_HIDE_IME = 3;
+ public static final int REQUEST_EDITTEXT_POSITION = 4;
+ public static final int REQUEST_DISPLAY_ID = 5;
}
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index ee53af1..012b0f2 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -116,6 +116,10 @@
"This decreases APK size at the cost of resource retrieval performance.\n"
"Applies sparse encoding to all resources regardless of minSdk.",
&options_.force_sparse_encoding);
+ AddOptionalSwitch(
+ "--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &options_.table_flattener_options.use_compact_entries);
AddOptionalSwitch("--collapse-resource-names",
"Collapses resource names to a single value in the key string pool. Resources can \n"
"be exempted using the \"no_collapse\" directive in a file specified by "
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 669cddb..382b088 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -18,9 +18,9 @@
#include <unordered_set>
-#include "android-base/logging.h"
-
#include "ResourceUtils.h"
+#include "android-base/logging.h"
+#include "process/SymbolTable.h"
#include "trace/TraceBuffer.h"
#include "util/Util.h"
#include "xml/XmlActionExecutor.h"
@@ -172,6 +172,59 @@
return NameIsJavaClassName(el, attr, diag);
}
+static bool UpdateConfigChangesIfNeeded(xml::Element* el, IAaptContext* context) {
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+ if (attr == nullptr) {
+ return true;
+ }
+
+ if (attr->value != "allKnown" && attr->value.find("allKnown") != std::string::npos) {
+ context->GetDiagnostics()->Error(
+ android::DiagMessage(el->line_number)
+ << "If you want to declare 'allKnown' in attribute 'android:configChanges' in <" << el->name
+ << ">, " << attr->value << " is not allowed', allKnown has to be used "
+ << "by itself, for example: 'android:configChanges=allKnown', it cannot be combined with "
+ << "the other flags");
+ return false;
+ }
+
+ if (attr->value == "allKnown") {
+ SymbolTable* symbol_table = context->GetExternalSymbols();
+ const SymbolTable::Symbol* symbol =
+ symbol_table->FindByName(ResourceName("android", ResourceType::kAttr, "configChanges"));
+
+ if (symbol == nullptr) {
+ context->GetDiagnostics()->Error(
+ android::DiagMessage(el->line_number)
+ << "Cannot find symbol for android:configChanges with min sdk: "
+ << context->GetMinSdkVersion());
+ return false;
+ }
+
+ std::stringstream new_value;
+
+ const auto& symbols = symbol->attribute->symbols;
+ for (auto it = symbols.begin(); it != symbols.end(); ++it) {
+ // Skip 'resourcesUnused' which is the flag to fully disable activity restart specifically
+ // for games.
+ if (it->symbol.name.value().entry == "resourcesUnused") {
+ continue;
+ }
+ if (it != symbols.begin()) {
+ new_value << "|";
+ }
+ new_value << it->symbol.name.value().entry;
+ }
+ const auto& old_value = attr->value;
+ auto new_value_str = new_value.str();
+ context->GetDiagnostics()->Note(android::DiagMessage(el->line_number)
+ << "Updating value of 'android:configChanges' from "
+ << old_value << " to " << new_value_str);
+ attr->value = std::move(new_value_str);
+ }
+ return true;
+}
+
static bool RequiredNameIsJavaPackage(xml::Element* el, android::SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
if (attr == nullptr) {
@@ -382,8 +435,9 @@
}
}
-bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag) {
+bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, IAaptContext* context) {
// First verify some options.
+ android::IDiagnostics* diag = context->GetDiagnostics();
if (options_.rename_manifest_package) {
if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) {
diag->Error(android::DiagMessage() << "invalid manifest package override '"
@@ -432,6 +486,8 @@
// Common component actions.
xml::XmlNodeAction component_action;
component_action.Action(RequiredNameIsJavaClassName);
+ component_action.Action(
+ [context](xml::Element* el) -> bool { return UpdateConfigChangesIfNeeded(el, context); });
component_action["intent-filter"] = intent_filter_action;
component_action["preferred"] = intent_filter_action;
component_action["meta-data"] = meta_data_action;
@@ -778,7 +834,7 @@
}
xml::XmlActionExecutor executor;
- if (!BuildRules(&executor, context->GetDiagnostics())) {
+ if (!BuildRules(&executor, context)) {
return false;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index df0ece6..748a828 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -110,7 +110,7 @@
private:
DISALLOW_COPY_AND_ASSIGN(ManifestFixer);
- bool BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag);
+ bool BuildRules(xml::XmlActionExecutor* executor, IAaptContext* context);
ManifestFixerOptions options_;
};
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 3cfdf78..5008627 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -37,27 +37,30 @@
.SetCompilationPackage("android")
.SetPackageId(0x01)
.SetNameManglerPolicy(NameManglerPolicy{"android"})
- .AddSymbolSource(
- test::StaticSymbolSourceBuilder()
- .AddSymbol(
- "android:attr/package", ResourceId(0x01010000),
- test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .Build())
- .AddSymbol(
- "android:attr/minSdkVersion", ResourceId(0x01010001),
- test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING |
- android::ResTable_map::TYPE_INTEGER)
- .Build())
- .AddSymbol(
- "android:attr/targetSdkVersion", ResourceId(0x01010002),
- test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING |
- android::ResTable_map::TYPE_INTEGER)
- .Build())
- .AddSymbol("android:string/str", ResourceId(0x01060000))
- .Build())
+ .AddSymbolSource(test::StaticSymbolSourceBuilder()
+ .AddSymbol("android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .Build())
+ .AddSymbol("android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .Build())
+ .AddSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .Build())
+ .AddSymbol("android:string/str", ResourceId(0x01060000))
+ .AddSymbol("android:attr/configChanges", ResourceId(0x01010003),
+ test::AttributeBuilder()
+ .AddItem("testConfigChange1", 1)
+ .AddItem("testConfigChange2", 2)
+ .AddItem("resourcesUnused", 4)
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .Build())
+ .Build())
.Build();
}
@@ -1591,4 +1594,72 @@
</manifest>)";
EXPECT_THAT(Verify(input), NotNull());
}
+
+TEST_F(ManifestFixerTest, AllKnownNotDeclaredProperly) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity"
+ android:configChanges="allKnown|testConfigChange1">
+ </activity>
+ </application>
+ </manifest>)";
+ auto doc = Verify(input);
+ EXPECT_THAT(doc, IsNull());
+}
+
+TEST_F(ManifestFixerTest, ModifyAttributeValueForAllKnown) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity"
+ android:configChanges="allKnown">
+ </activity>
+ </application>
+ </manifest>)";
+ auto doc = Verify(input);
+ EXPECT_THAT(doc, NotNull());
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = doc->root.get();
+ ASSERT_THAT(el, NotNull());
+ el = el->FindChild({}, "application");
+ ASSERT_THAT(el, NotNull());
+ el = el->FindChild({}, "activity");
+ ASSERT_THAT(el, NotNull());
+
+ attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+ ASSERT_THAT(attr->value, "testConfigChange1|testConfigChange2");
+}
+
+TEST_F(ManifestFixerTest, DoNothingForOtherConfigChanges) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity"
+ android:configChanges="testConfigChange2">
+ </activity>
+ </application>
+ </manifest>)";
+ auto doc = Verify(input);
+ EXPECT_THAT(doc, NotNull());
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = doc->root.get();
+ ASSERT_THAT(el, NotNull());
+ el = el->FindChild({}, "application");
+ ASSERT_THAT(el, NotNull());
+ el = el->FindChild({}, "activity");
+ ASSERT_THAT(el, NotNull());
+
+ attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+ ASSERT_THAT(attr->value, "testConfigChange2");
+}
} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 02e4beae..8ae55b8 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -189,7 +189,7 @@
base->append(part.data(), part.size());
}
-std::string BuildPath(std::vector<const StringPiece>&& args) {
+std::string BuildPath(const std::vector<StringPiece>& args) {
if (args.empty()) {
return "";
}
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 42eeaf2..c1a42446 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -60,7 +60,7 @@
void AppendPath(std::string* base, android::StringPiece part);
// Concatenates the list of paths and separates each part with the directory separator.
-std::string BuildPath(std::vector<const android::StringPiece>&& args);
+std::string BuildPath(const std::vector<android::StringPiece>& args);
// Makes all the directories in `path`. The last element in the path is interpreted as a directory.
bool mkdirs(const std::string& path);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index 191f38d..718d898 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -62,13 +62,11 @@
XmlUtils.getSingleChildElement(
document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES, true);
- return new AndroidSafetyLabelFactory()
- .createFromHrElements(XmlUtils.listOf(appMetadataBundles));
+ return new AndroidSafetyLabelFactory().createFromHrElement(appMetadataBundles);
case ON_DEVICE:
Element bundleEle =
XmlUtils.getSingleChildElement(document, XmlUtils.OD_TAG_BUNDLE, true);
- return new AndroidSafetyLabelFactory()
- .createFromOdElements(XmlUtils.listOf(bundleEle));
+ return new AndroidSafetyLabelFactory().createFromOdElement(bundleEle);
default:
throw new IllegalStateException("Unrecognized input format.");
}
@@ -91,14 +89,10 @@
switch (format) {
case HUMAN_READABLE:
- for (var child : asl.toHrDomElements(document)) {
- document.appendChild(child);
- }
+ document.appendChild(asl.toHrDomElement(document));
break;
case ON_DEVICE:
- for (var child : asl.toOdDomElements(document)) {
- document.appendChild(child);
- }
+ document.appendChild(asl.toOdDomElement(document));
break;
default:
throw new IllegalStateException("Unrecognized input format.");
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index 72140a1..8b2fd93 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -21,8 +21,6 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
public class AndroidSafetyLabel implements AslMarshallable {
private final Long mVersion;
@@ -46,36 +44,34 @@
}
/** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
aslEle.appendChild(XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
if (mSafetyLabels != null) {
- XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
+ aslEle.appendChild(mSafetyLabels.toOdDomElement(doc));
}
if (mSystemAppSafetyLabel != null) {
- XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toOdDomElements(doc));
+ aslEle.appendChild(mSystemAppSafetyLabel.toOdDomElement(doc));
}
if (mTransparencyInfo != null) {
- XmlUtils.appendChildren(aslEle, mTransparencyInfo.toOdDomElements(doc));
+ aslEle.appendChild(mTransparencyInfo.toOdDomElement(doc));
}
- return XmlUtils.listOf(aslEle);
+ return aslEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element aslEle = doc.createElement(XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
aslEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion));
if (mSafetyLabels != null) {
- XmlUtils.appendChildren(aslEle, mSafetyLabels.toHrDomElements(doc));
+ aslEle.appendChild(mSafetyLabels.toHrDomElement(doc));
}
if (mSystemAppSafetyLabel != null) {
- XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toHrDomElements(doc));
+ aslEle.appendChild(mSystemAppSafetyLabel.toHrDomElement(doc));
}
if (mTransparencyInfo != null) {
- XmlUtils.appendChildren(aslEle, mTransparencyInfo.toHrDomElements(doc));
+ aslEle.appendChild(mTransparencyInfo.toHrDomElement(doc));
}
- return XmlUtils.listOf(aslEle);
+ return aslEle;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index c53cbbf..b9eb2a35 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -21,65 +21,117 @@
import org.w3c.dom.Element;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class AndroidSafetyLabelFactory implements AslMarshallableFactory<AndroidSafetyLabel> {
+ private final Map<Long, Set<String>> mRecognizedHrAttrs =
+ Map.ofEntries(Map.entry(1L, Set.of(XmlUtils.HR_ATTR_VERSION)));
+ private final Map<Long, Set<String>> mRequiredHrAttrs =
+ Map.ofEntries(Map.entry(1L, Set.of(XmlUtils.HR_ATTR_VERSION)));
+ private final Map<Long, Set<String>> mRecognizedHrEles =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.HR_TAG_SAFETY_LABELS,
+ XmlUtils.HR_TAG_TRANSPARENCY_INFO)));
+ private final Map<Long, Set<String>> mRequiredHrEles =
+ Map.ofEntries(
+ Map.entry(1L, Set.of()),
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.HR_TAG_TRANSPARENCY_INFO)));
+ private final Map<Long, Set<String>> mRecognizedOdEleNames =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.OD_NAME_VERSION,
+ XmlUtils.OD_NAME_SAFETY_LABELS,
+ XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.OD_NAME_TRANSPARENCY_INFO)));
+ private final Map<Long, Set<String>> mRequiredOdEleNames =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.OD_NAME_VERSION)),
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.OD_NAME_VERSION,
+ XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.OD_NAME_TRANSPARENCY_INFO)));
/** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */
- @Override
- public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles)
+ public AndroidSafetyLabel createFromHrElement(Element appMetadataBundlesEle)
throws MalformedXmlException {
- Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
long version = XmlUtils.tryGetVersion(appMetadataBundlesEle);
+ XmlUtils.throwIfExtraneousAttributes(
+ appMetadataBundlesEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+ XmlUtils.throwIfExtraneousChildrenHr(
+ appMetadataBundlesEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
Element safetyLabelsEle =
XmlUtils.getSingleChildElement(
- appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS, false);
+ appMetadataBundlesEle,
+ XmlUtils.HR_TAG_SAFETY_LABELS,
+ XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
SafetyLabels safetyLabels =
- new SafetyLabelsFactory().createFromHrElements(XmlUtils.listOf(safetyLabelsEle));
+ new SafetyLabelsFactory().createFromHrElement(safetyLabelsEle, version);
Element systemAppSafetyLabelEle =
XmlUtils.getSingleChildElement(
- appMetadataBundlesEle, XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL, false);
+ appMetadataBundlesEle,
+ XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
SystemAppSafetyLabel systemAppSafetyLabel =
new SystemAppSafetyLabelFactory()
- .createFromHrElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+ .createFromHrElement(systemAppSafetyLabelEle, version);
Element transparencyInfoEle =
XmlUtils.getSingleChildElement(
- appMetadataBundlesEle, XmlUtils.HR_TAG_TRANSPARENCY_INFO, false);
+ appMetadataBundlesEle,
+ XmlUtils.HR_TAG_TRANSPARENCY_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
TransparencyInfo transparencyInfo =
- new TransparencyInfoFactory()
- .createFromHrElements(XmlUtils.listOf(transparencyInfoEle));
+ new TransparencyInfoFactory().createFromHrElement(transparencyInfoEle, version);
return new AndroidSafetyLabel(
version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
}
/** Creates an {@link AndroidSafetyLabel} from on-device DOM elements */
- @Override
- public AndroidSafetyLabel createFromOdElements(List<Element> elements)
- throws MalformedXmlException {
- Element bundleEle = XmlUtils.getSingleElement(elements);
+ public AndroidSafetyLabel createFromOdElement(Element bundleEle) throws MalformedXmlException {
Long version = XmlUtils.getOdLongEle(bundleEle, XmlUtils.OD_NAME_VERSION, true);
+ XmlUtils.throwIfExtraneousChildrenOd(
+ bundleEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
Element safetyLabelsEle =
- XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_SAFETY_LABELS, false);
+ XmlUtils.getOdPbundleWithName(
+ bundleEle,
+ XmlUtils.OD_NAME_SAFETY_LABELS,
+ XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
SafetyLabels safetyLabels =
- new SafetyLabelsFactory().createFromOdElements(XmlUtils.listOf(safetyLabelsEle));
+ new SafetyLabelsFactory().createFromOdElement(safetyLabelsEle, version);
Element systemAppSafetyLabelEle =
XmlUtils.getOdPbundleWithName(
- bundleEle, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL, false);
+ bundleEle,
+ XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL,
+ XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
SystemAppSafetyLabel systemAppSafetyLabel =
new SystemAppSafetyLabelFactory()
- .createFromOdElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+ .createFromOdElement(systemAppSafetyLabelEle, version);
Element transparencyInfoEle =
- XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_TRANSPARENCY_INFO, false);
+ XmlUtils.getOdPbundleWithName(
+ bundleEle,
+ XmlUtils.OD_NAME_TRANSPARENCY_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
TransparencyInfo transparencyInfo =
- new TransparencyInfoFactory()
- .createFromOdElements(XmlUtils.listOf(transparencyInfoEle));
+ new TransparencyInfoFactory().createFromOdElement(transparencyInfoEle, version);
return new AndroidSafetyLabel(
version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index f397225..d2557ae 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -25,36 +25,107 @@
/** AppInfo representation */
public class AppInfo implements AslMarshallable {
- private final Boolean mApsCompliant;
+ private final String mTitle;
+ private final String mDescription;
+ private final Boolean mContainsAds;
+ private final Boolean mObeyAps;
+ private final Boolean mAdsFingerprinting;
+ private final Boolean mSecurityFingerprinting;
private final String mPrivacyPolicy;
+ private final List<String> mSecurityEndpoints;
private final List<String> mFirstPartyEndpoints;
private final List<String> mServiceProviderEndpoints;
+ private final String mCategory;
+ private final String mEmail;
+ private final String mWebsite;
+
+ private final Boolean mApsCompliant;
+ private final String mDeveloperId;
+ private final String mApplicationId;
+
+ // private final String mPrivacyPolicy;
+ // private final List<String> mFirstPartyEndpoints;
+ // private final List<String> mServiceProviderEndpoints;
public AppInfo(
- Boolean apsCompliant,
+ String title,
+ String description,
+ Boolean containsAds,
+ Boolean obeyAps,
+ Boolean adsFingerprinting,
+ Boolean securityFingerprinting,
String privacyPolicy,
+ List<String> securityEndpoints,
List<String> firstPartyEndpoints,
- List<String> serviceProviderEndpoints) {
- this.mApsCompliant = apsCompliant;
+ List<String> serviceProviderEndpoints,
+ String category,
+ String email,
+ String website,
+ Boolean apsCompliant,
+ String developerId,
+ String applicationId) {
+ this.mTitle = title;
+ this.mDescription = description;
+ this.mContainsAds = containsAds;
+ this.mObeyAps = obeyAps;
+ this.mAdsFingerprinting = adsFingerprinting;
+ this.mSecurityFingerprinting = securityFingerprinting;
this.mPrivacyPolicy = privacyPolicy;
+ this.mSecurityEndpoints = securityEndpoints;
this.mFirstPartyEndpoints = firstPartyEndpoints;
this.mServiceProviderEndpoints = serviceProviderEndpoints;
+ this.mCategory = category;
+ this.mEmail = email;
+ this.mWebsite = website;
+ this.mApsCompliant = apsCompliant;
+ this.mDeveloperId = developerId;
+ this.mApplicationId = applicationId;
}
/** Creates an on-device DOM element from the {@link SafetyLabels}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO);
- if (this.mApsCompliant != null) {
+
+ if (this.mTitle != null) {
+ appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle));
+ }
+ if (this.mDescription != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription));
+ }
+ if (this.mContainsAds != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds));
+ }
+ if (this.mObeyAps != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps));
+ }
+ if (this.mAdsFingerprinting != null) {
appInfoEle.appendChild(
XmlUtils.createOdBooleanEle(
- doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant));
+ doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting));
+ }
+ if (this.mSecurityFingerprinting != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc,
+ XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+ mSecurityFingerprinting));
}
if (this.mPrivacyPolicy != null) {
appInfoEle.appendChild(
XmlUtils.createOdStringEle(
doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy));
}
+ if (this.mSecurityEndpoints != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdArray(
+ doc,
+ XmlUtils.OD_TAG_STRING_ARRAY,
+ XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+ mSecurityEndpoints));
+ }
if (this.mFirstPartyEndpoints != null) {
appInfoEle.appendChild(
XmlUtils.createOdArray(
@@ -71,27 +142,88 @@
XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
mServiceProviderEndpoints));
}
- return XmlUtils.listOf(appInfoEle);
+ if (this.mCategory != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory));
+ }
+ if (this.mEmail != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail));
+ }
+ if (this.mWebsite != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite));
+ }
+
+ if (this.mApsCompliant != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant));
+ }
+ if (this.mDeveloperId != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DEVELOPER_ID, mDeveloperId));
+ }
+ if (this.mApplicationId != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc, XmlUtils.OD_NAME_APPLICATION_ID, mApplicationId));
+ }
+ return appInfoEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element appInfoEle = doc.createElement(XmlUtils.HR_TAG_APP_INFO);
- if (this.mApsCompliant != null) {
+
+ if (this.mTitle != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_TITLE, this.mTitle);
+ }
+ if (this.mDescription != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_DESCRIPTION, this.mDescription);
+ }
+ if (this.mContainsAds != null) {
appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant));
+ XmlUtils.HR_ATTR_CONTAINS_ADS, String.valueOf(this.mContainsAds));
+ }
+ if (this.mObeyAps != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_OBEY_APS, String.valueOf(this.mObeyAps));
+ }
+ if (this.mAdsFingerprinting != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_ADS_FINGERPRINTING, String.valueOf(this.mAdsFingerprinting));
+ }
+ if (this.mSecurityFingerprinting != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING,
+ String.valueOf(this.mSecurityFingerprinting));
}
if (this.mPrivacyPolicy != null) {
appInfoEle.setAttribute(XmlUtils.HR_ATTR_PRIVACY_POLICY, this.mPrivacyPolicy);
}
+ if (this.mSecurityEndpoints != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, String.join("|", this.mSecurityEndpoints));
+ }
+ if (this.mCategory != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_CATEGORY, this.mCategory);
+ }
+ if (this.mEmail != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, this.mEmail);
+ }
+ if (this.mWebsite != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, this.mWebsite);
+ }
+ if (this.mApsCompliant != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant));
+ }
if (this.mFirstPartyEndpoints != null) {
appInfoEle.appendChild(
XmlUtils.createHrArray(
doc, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, mFirstPartyEndpoints));
}
-
if (this.mServiceProviderEndpoints != null) {
appInfoEle.appendChild(
XmlUtils.createHrArray(
@@ -99,7 +231,13 @@
XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS,
mServiceProviderEndpoints));
}
+ if (this.mDeveloperId != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_DEVELOPER_ID, this.mDeveloperId);
+ }
+ if (this.mApplicationId != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_APPLICATION_ID, this.mApplicationId);
+ }
- return XmlUtils.listOf(appInfoEle);
+ return appInfoEle;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 6ad2027..277a508 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -16,60 +16,233 @@
package com.android.asllib.marshallable;
-import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
+ // We don't need to support V1 for HR.
+ private final Map<Long, Set<String>> mRecognizedHrAttrs =
+ Map.ofEntries(
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.HR_ATTR_APS_COMPLIANT,
+ XmlUtils.HR_ATTR_PRIVACY_POLICY,
+ XmlUtils.HR_ATTR_DEVELOPER_ID,
+ XmlUtils.HR_ATTR_APPLICATION_ID)));
+ private final Map<Long, Set<String>> mRequiredHrAttrs =
+ Map.ofEntries(
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.HR_ATTR_APS_COMPLIANT,
+ XmlUtils.HR_ATTR_PRIVACY_POLICY,
+ XmlUtils.HR_ATTR_DEVELOPER_ID,
+ XmlUtils.HR_ATTR_APPLICATION_ID)));
+ private final Map<Long, Set<String>> mRecognizedHrEles =
+ Map.ofEntries(
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS)));
+ private final Map<Long, Set<String>> mRequiredHrEles =
+ Map.ofEntries(
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS)));
+ private final Map<Long, Set<String>> mRecognizedOdEleNames =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.OD_NAME_TITLE,
+ XmlUtils.OD_NAME_DESCRIPTION,
+ XmlUtils.OD_NAME_CONTAINS_ADS,
+ XmlUtils.OD_NAME_OBEY_APS,
+ XmlUtils.OD_NAME_ADS_FINGERPRINTING,
+ XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+ XmlUtils.OD_NAME_PRIVACY_POLICY,
+ XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+ XmlUtils.OD_NAME_CATEGORY,
+ XmlUtils.OD_NAME_EMAIL,
+ XmlUtils.OD_NAME_WEBSITE)),
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.OD_NAME_APS_COMPLIANT,
+ XmlUtils.OD_NAME_PRIVACY_POLICY,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+ XmlUtils.OD_NAME_DEVELOPER_ID,
+ XmlUtils.OD_NAME_APPLICATION_ID)));
+ private final Map<Long, Set<String>> mRequiredOdEles =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.OD_NAME_TITLE,
+ XmlUtils.OD_NAME_DESCRIPTION,
+ XmlUtils.OD_NAME_CONTAINS_ADS,
+ XmlUtils.OD_NAME_OBEY_APS,
+ XmlUtils.OD_NAME_ADS_FINGERPRINTING,
+ XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+ XmlUtils.OD_NAME_PRIVACY_POLICY,
+ XmlUtils.OD_NAME_SECURITY_ENDPOINTS,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+ XmlUtils.OD_NAME_CATEGORY)),
+ Map.entry(
+ 2L,
+ Set.of(
+ XmlUtils.OD_NAME_APS_COMPLIANT,
+ XmlUtils.OD_NAME_PRIVACY_POLICY,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
+ XmlUtils.OD_NAME_DEVELOPER_ID,
+ XmlUtils.OD_NAME_APPLICATION_ID)));
/** Creates a {@link AppInfo} from the human-readable DOM element. */
- @Override
- public AppInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
- Element appInfoEle = XmlUtils.getSingleElement(elements);
- if (appInfoEle == null) {
- AslgenUtil.logI("No AppInfo found in hr format.");
- return null;
- }
+ public AppInfo createFromHrElement(Element appInfoEle, long version)
+ throws MalformedXmlException {
+ XmlUtils.throwIfExtraneousAttributes(
+ appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+ XmlUtils.throwIfExtraneousChildrenHr(
+ appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+
+ var requiredHrAttrs = XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version);
+ var requiredHrEles = XmlUtils.getMostRecentVersion(mRequiredHrEles, version);
+
+ String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE, requiredHrAttrs);
+ String description =
+ XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION, requiredHrAttrs);
+ Boolean containsAds =
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, requiredHrAttrs);
+ Boolean obeyAps =
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, requiredHrAttrs);
+ Boolean adsFingerprinting =
+ XmlUtils.getBoolAttr(
+ appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, requiredHrAttrs);
+ Boolean securityFingerprinting =
+ XmlUtils.getBoolAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, requiredHrAttrs);
+ String privacyPolicy =
+ XmlUtils.getStringAttr(
+ appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, requiredHrAttrs);
+ List<String> securityEndpoints =
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, requiredHrAttrs);
+ String category =
+ XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY, requiredHrAttrs);
+ String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL, requiredHrAttrs);
+ String website =
+ XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, requiredHrAttrs);
+ String developerId =
+ XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DEVELOPER_ID, requiredHrAttrs);
+ String applicationId =
+ XmlUtils.getStringAttr(
+ appInfoEle, XmlUtils.HR_ATTR_APPLICATION_ID, requiredHrAttrs);
Boolean apsCompliant =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, true);
- String privacyPolicy =
- XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, true);
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, requiredHrAttrs);
List<String> firstPartyEndpoints =
XmlUtils.getHrItemsAsStrings(
- appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, true);
+ appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, requiredHrEles);
List<String> serviceProviderEndpoints =
XmlUtils.getHrItemsAsStrings(
- appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, true);
+ appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, requiredHrEles);
return new AppInfo(
- apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
+ title,
+ description,
+ containsAds,
+ obeyAps,
+ adsFingerprinting,
+ securityFingerprinting,
+ privacyPolicy,
+ securityEndpoints,
+ firstPartyEndpoints,
+ serviceProviderEndpoints,
+ category,
+ email,
+ website,
+ apsCompliant,
+ developerId,
+ applicationId);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element appInfoEle = XmlUtils.getSingleElement(elements);
- if (appInfoEle == null) {
- AslgenUtil.logI("No AppInfo found in od format.");
- return null;
- }
+ public AppInfo createFromOdElement(Element appInfoEle, long version)
+ throws MalformedXmlException {
+ XmlUtils.throwIfExtraneousChildrenOd(
+ appInfoEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+ var requiredOdEles = XmlUtils.getMostRecentVersion(mRequiredOdEles, version);
+
+ String title = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_TITLE, requiredOdEles);
+ String description =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DESCRIPTION, requiredOdEles);
+ Boolean containsAds =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_CONTAINS_ADS, requiredOdEles);
+ Boolean obeyAps =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_OBEY_APS, requiredOdEles);
+ Boolean adsFingerprinting =
+ XmlUtils.getOdBoolEle(
+ appInfoEle, XmlUtils.OD_NAME_ADS_FINGERPRINTING, requiredOdEles);
+ Boolean securityFingerprinting =
+ XmlUtils.getOdBoolEle(
+ appInfoEle, XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, requiredOdEles);
+ String privacyPolicy =
+ XmlUtils.getOdStringEle(
+ appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, requiredOdEles);
+ List<String> securityEndpoints =
+ XmlUtils.getOdStringArray(
+ appInfoEle, XmlUtils.OD_NAME_SECURITY_ENDPOINTS, requiredOdEles);
+ String category =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_CATEGORY, requiredOdEles);
+ String email = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_EMAIL, requiredOdEles);
+ String website =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_WEBSITE, requiredOdEles);
+ String developerId =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DEVELOPER_ID, requiredOdEles);
+ String applicationId =
+ XmlUtils.getOdStringEle(
+ appInfoEle, XmlUtils.OD_NAME_APPLICATION_ID, requiredOdEles);
Boolean apsCompliant =
- XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, true);
- String privacyPolicy =
- XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, true);
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, requiredOdEles);
List<String> firstPartyEndpoints =
- XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, true);
+ XmlUtils.getOdStringArray(
+ appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, requiredOdEles);
List<String> serviceProviderEndpoints =
XmlUtils.getOdStringArray(
- appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, true);
+ appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, requiredOdEles);
return new AppInfo(
- apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
+ title,
+ description,
+ containsAds,
+ obeyAps,
+ adsFingerprinting,
+ securityFingerprinting,
+ privacyPolicy,
+ securityEndpoints,
+ firstPartyEndpoints,
+ serviceProviderEndpoints,
+ category,
+ email,
+ website,
+ apsCompliant,
+ developerId,
+ applicationId);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 0a70e7d0..b6c789d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -16,16 +16,11 @@
package com.android.asllib.marshallable;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.util.List;
-
public interface AslMarshallable {
/** Creates the on-device DOM elements from the AslMarshallable Java Object. */
- List<Element> toOdDomElements(Document doc);
+ // List<Element> toOdDomElements(Document doc);
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- List<Element> toHrDomElements(Document doc);
+ // List<Element> toHrDomElements(Document doc);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index 3958290..67f1069 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -16,17 +16,11 @@
package com.android.asllib.marshallable;
-import com.android.asllib.util.MalformedXmlException;
-
-import org.w3c.dom.Element;
-
-import java.util.List;
-
public interface AslMarshallableFactory<T extends AslMarshallable> {
/** Creates an {@link AslMarshallableFactory} from human-readable DOM elements */
- T createFromHrElements(List<Element> elements) throws MalformedXmlException;
+ // T createFromHrElements(List<Element> elements) throws MalformedXmlException;
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- T createFromOdElements(List<Element> elements) throws MalformedXmlException;
+ // T createFromOdElements(List<Element> elements) throws MalformedXmlException;
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index c16d18b..501f170 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -57,18 +57,16 @@
}
/** Creates on-device DOM element(s) from the {@link DataCategory}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element dataCategoryEle = XmlUtils.createPbundleEleWithName(doc, this.getCategoryName());
for (DataType dataType : mDataTypes.values()) {
- XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
+ dataCategoryEle.appendChild(dataType.toOdDomElement(doc));
}
- return XmlUtils.listOf(dataCategoryEle);
+ return dataCategoryEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public List<Element> toHrDomElement(Document doc) {
throw new IllegalStateException(
"Turning DataCategory or DataType into human-readable DOM elements requires"
+ " visibility into parent elements. The logic resides in DataLabels.");
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index 7244162..fb84e50 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -23,13 +23,11 @@
import org.w3c.dom.Element;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
public class DataCategoryFactory {
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element dataCategoryEle = XmlUtils.getSingleElement(elements);
+ public DataCategory createFromOdElement(Element dataCategoryEle) throws MalformedXmlException {
Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
String categoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
var odDataTypes = XmlUtils.asElementList(dataCategoryEle.getChildNodes());
@@ -45,9 +43,7 @@
"Unrecognized data type name %s for category %s",
dataTypeName, categoryName));
}
- dataTypeMap.put(
- dataTypeName,
- new DataTypeFactory().createFromOdElements(XmlUtils.listOf(odDataTypeEle)));
+ dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromOdElement(odDataTypeEle));
}
return new DataCategory(categoryName, dataTypeMap);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 3c93c88..2cf7c82f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -22,7 +22,6 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -57,20 +56,18 @@
}
/** Gets the on-device DOM element for the {@link DataLabels}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element dataLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
- return XmlUtils.listOf(dataLabelsEle);
+ return dataLabelsEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS);
maybeAppendHrDataUsages(
doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED, false);
@@ -78,7 +75,7 @@
doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true);
maybeAppendHrDataUsages(
doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED, false);
- return XmlUtils.listOf(dataLabelsEle);
+ return dataLabelsEle;
}
private void maybeAppendDataUsages(
@@ -96,7 +93,7 @@
DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
- XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
+ dataCategoryEle.appendChild(dataType.toOdDomElement(doc));
}
dataUsageEle.appendChild(dataCategoryEle);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index c4d8876..b1cf3ea0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -31,9 +31,7 @@
public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
/** Creates a {@link DataLabels} from the human-readable DOM element. */
- @Override
- public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
- Element ele = XmlUtils.getSingleElement(elements);
+ public DataLabels createFromHrElement(Element ele) throws MalformedXmlException {
if (ele == null) {
AslgenUtil.logI("Found no DataLabels in hr format.");
return null;
@@ -83,9 +81,7 @@
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public DataLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element dataLabelsEle = XmlUtils.getSingleElement(elements);
+ public DataLabels createFromOdElement(Element dataLabelsEle) throws MalformedXmlException {
if (dataLabelsEle == null) {
AslgenUtil.logI("Found no DataLabels in od format.");
return null;
@@ -111,7 +107,7 @@
for (Element dataCategoryEle : dataCategoryEles) {
String dataCategoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
DataCategory dataCategory =
- new DataCategoryFactory().createFromOdElements(List.of(dataCategoryEle));
+ new DataCategoryFactory().createFromOdElement(dataCategoryEle);
dataCategoryMap.put(dataCategoryName, dataCategory);
}
return dataCategoryMap;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index 284a4b8..83bb611 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -172,8 +172,8 @@
return mEphemeral;
}
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ /** Gets the on-device dom element */
+ public Element toOdDomElement(Document doc) {
Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, this.getDataTypeName());
if (!this.getPurposes().isEmpty()) {
dataTypeEle.appendChild(
@@ -197,11 +197,10 @@
this.getIsSharingOptional(),
XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
- return XmlUtils.listOf(dataTypeEle);
+ return dataTypeEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
public List<Element> toHrDomElements(Document doc) {
throw new IllegalStateException(
"Turning DataCategory or DataType into human-readable DOM elements requires"
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index a5559d8..96a58fa 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -64,8 +64,7 @@
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element odDataTypeEle = XmlUtils.getSingleElement(elements);
+ public DataType createFromOdElement(Element odDataTypeEle) throws MalformedXmlException {
String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
List<Integer> purposeInts =
XmlUtils.getOdIntArray(odDataTypeEle, XmlUtils.OD_NAME_PURPOSES, true);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
new file mode 100644
index 0000000..96a64dc
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/** DeveloperInfo representation */
+public class DeveloperInfo implements AslMarshallable {
+ public enum DeveloperRelationship {
+ OEM(0),
+ ODM(1),
+ SOC(2),
+ OTA(3),
+ CARRIER(4),
+ AOSP(5),
+ OTHER(6);
+
+ private final int mValue;
+
+ DeveloperRelationship(int value) {
+ this.mValue = value;
+ }
+
+ /** Get the int value associated with the DeveloperRelationship. */
+ public int getValue() {
+ return mValue;
+ }
+
+ /** Get the DeveloperRelationship associated with the int value. */
+ public static DeveloperInfo.DeveloperRelationship forValue(int value) {
+ for (DeveloperInfo.DeveloperRelationship e : values()) {
+ if (e.getValue() == value) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value);
+ }
+
+ /** Get the DeveloperRelationship associated with the human-readable String. */
+ public static DeveloperInfo.DeveloperRelationship forString(String s) {
+ for (DeveloperInfo.DeveloperRelationship e : values()) {
+ if (e.toString().equals(s)) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s);
+ }
+
+ /** Human-readable String representation of DeveloperRelationship. */
+ public String toString() {
+ return this.name().toLowerCase();
+ }
+ }
+
+ private final String mName;
+ private final String mEmail;
+ private final String mAddress;
+ private final String mCountryRegion;
+ private final DeveloperRelationship mDeveloperRelationship;
+ private final String mWebsite;
+ private final String mAppDeveloperRegistryId;
+
+ public DeveloperInfo(
+ String name,
+ String email,
+ String address,
+ String countryRegion,
+ DeveloperRelationship developerRelationship,
+ String website,
+ String appDeveloperRegistryId) {
+ this.mName = name;
+ this.mEmail = email;
+ this.mAddress = address;
+ this.mCountryRegion = countryRegion;
+ this.mDeveloperRelationship = developerRelationship;
+ this.mWebsite = website;
+ this.mAppDeveloperRegistryId = appDeveloperRegistryId;
+ }
+
+ /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+ public Element toOdDomElement(Document doc) {
+ Element developerInfoEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO);
+ if (mName != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName));
+ }
+ if (mEmail != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail));
+ }
+ if (mAddress != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress));
+ }
+ if (mCountryRegion != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion));
+ }
+ if (mDeveloperRelationship != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdLongEle(
+ doc,
+ XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+ mDeveloperRelationship.getValue()));
+ }
+ if (mWebsite != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite));
+ }
+ if (mAppDeveloperRegistryId != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc,
+ XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID,
+ mAppDeveloperRegistryId));
+ }
+ return developerInfoEle;
+ }
+
+ /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+ public Element toHrDomElement(Document doc) {
+ Element developerInfoEle = doc.createElement(XmlUtils.HR_TAG_DEVELOPER_INFO);
+ if (mName != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_NAME, mName);
+ }
+ if (mEmail != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, mEmail);
+ }
+ if (mAddress != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_ADDRESS, mAddress);
+ }
+ if (mCountryRegion != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_COUNTRY_REGION, mCountryRegion);
+ }
+ if (mDeveloperRelationship != null) {
+ developerInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, mDeveloperRelationship.toString());
+ }
+ if (mWebsite != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, mWebsite);
+ }
+ if (mAppDeveloperRegistryId != null) {
+ developerInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, mAppDeveloperRegistryId);
+ }
+ return developerInfoEle;
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
new file mode 100644
index 0000000..e82a53a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -0,0 +1,89 @@
+/*
+ * 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.asllib.marshallable;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Element;
+
+public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> {
+ /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */
+ public DeveloperInfo createFromHrElement(Element developerInfoEle)
+ throws MalformedXmlException {
+ if (developerInfoEle == null) {
+ AslgenUtil.logI("No DeveloperInfo found in hr format.");
+ return null;
+ }
+ String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME, true);
+ String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
+ String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS, true);
+ String countryRegion =
+ XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION, true);
+ DeveloperInfo.DeveloperRelationship developerRelationship =
+ DeveloperInfo.DeveloperRelationship.forString(
+ XmlUtils.getStringAttr(
+ developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, true));
+ String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+ String appDeveloperRegistryId =
+ XmlUtils.getStringAttr(
+ developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false);
+ return new DeveloperInfo(
+ name,
+ email,
+ address,
+ countryRegion,
+ developerRelationship,
+ website,
+ appDeveloperRegistryId);
+ }
+
+ /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ public DeveloperInfo createFromOdElement(Element developerInfoEle)
+ throws MalformedXmlException {
+ if (developerInfoEle == null) {
+ AslgenUtil.logI("No DeveloperInfo found in od format.");
+ return null;
+ }
+ String name = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_NAME, true);
+ String email = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_EMAIL, true);
+ String address = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_ADDRESS, true);
+ String countryRegion =
+ XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_COUNTRY_REGION, true);
+ DeveloperInfo.DeveloperRelationship developerRelationship =
+ DeveloperInfo.DeveloperRelationship.forValue(
+ (int)
+ (long)
+ XmlUtils.getOdLongEle(
+ developerInfoEle,
+ XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+ true));
+ String website = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
+ String appDeveloperRegistryId =
+ XmlUtils.getOdStringEle(
+ developerInfoEle, XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, false);
+ return new DeveloperInfo(
+ name,
+ email,
+ address,
+ countryRegion,
+ developerRelationship,
+ website,
+ appDeveloperRegistryId);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 2a4e130..1a83c02 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -21,40 +21,50 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
public class SafetyLabels implements AslMarshallable {
private final DataLabels mDataLabels;
+ private final SecurityLabels mSecurityLabels;
+ private final ThirdPartyVerification mThirdPartyVerification;
- public SafetyLabels(DataLabels dataLabels) {
+ public SafetyLabels(
+ DataLabels dataLabels,
+ SecurityLabels securityLabels,
+ ThirdPartyVerification thirdPartyVerification) {
this.mDataLabels = dataLabels;
- }
-
- /** Returns the data label for the safety label */
- public DataLabels getDataLabel() {
- return mDataLabels;
+ this.mSecurityLabels = securityLabels;
+ this.mThirdPartyVerification = thirdPartyVerification;
}
/** Creates an on-device DOM element from the {@link SafetyLabels}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element safetyLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
if (mDataLabels != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
+ safetyLabelsEle.appendChild(mDataLabels.toOdDomElement(doc));
}
- return XmlUtils.listOf(safetyLabelsEle);
+ if (mSecurityLabels != null) {
+ safetyLabelsEle.appendChild(mSecurityLabels.toOdDomElement(doc));
+ }
+ if (mThirdPartyVerification != null) {
+ safetyLabelsEle.appendChild(mThirdPartyVerification.toOdDomElement(doc));
+ }
+ return safetyLabelsEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element safetyLabelsEle = doc.createElement(XmlUtils.HR_TAG_SAFETY_LABELS);
if (mDataLabels != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toHrDomElements(doc));
+ safetyLabelsEle.appendChild(mDataLabels.toHrDomElement(doc));
}
- return XmlUtils.listOf(safetyLabelsEle);
+ if (mSecurityLabels != null) {
+ safetyLabelsEle.appendChild(mSecurityLabels.toHrDomElement(doc));
+ }
+ if (mThirdPartyVerification != null) {
+ safetyLabelsEle.appendChild(mThirdPartyVerification.toHrDomElement(doc));
+ }
+ return safetyLabelsEle;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 2738337..35804d9 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -16,54 +16,109 @@
package com.android.asllib.marshallable;
-import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> {
+ private final Map<Long, Set<String>> mRecognizedHrAttrs =
+ Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRequiredHrAttrs = Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRecognizedHrEles =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.HR_TAG_DATA_LABELS,
+ XmlUtils.HR_TAG_SECURITY_LABELS,
+ XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION)),
+ Map.entry(2L, Set.of(XmlUtils.HR_TAG_DATA_LABELS)));
+ private final Map<Long, Set<String>> mRequiredHrEles = Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRecognizedOdEleNames =
+ Map.ofEntries(
+ Map.entry(
+ 1L,
+ Set.of(
+ XmlUtils.OD_NAME_DATA_LABELS,
+ XmlUtils.OD_NAME_SECURITY_LABELS,
+ XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION)),
+ Map.entry(2L, Set.of(XmlUtils.OD_NAME_DATA_LABELS)));
+ private final Map<Long, Set<String>> mRequiredOdEles = Map.ofEntries(Map.entry(1L, Set.of()));
/** Creates a {@link SafetyLabels} from the human-readable DOM element. */
- @Override
- public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
- Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+ public SafetyLabels createFromHrElement(Element safetyLabelsEle, long version)
+ throws MalformedXmlException {
if (safetyLabelsEle == null) {
- AslgenUtil.logI("No SafetyLabels found in hr format.");
return null;
}
+ XmlUtils.throwIfExtraneousAttributes(
+ safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+ XmlUtils.throwIfExtraneousChildrenHr(
+ safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+
+ var requiredHrEles = XmlUtils.getMostRecentVersion(mRequiredHrEles, version);
+
DataLabels dataLabels =
new DataLabelsFactory()
- .createFromHrElements(
- XmlUtils.listOf(
- XmlUtils.getSingleChildElement(
- safetyLabelsEle,
- XmlUtils.HR_TAG_DATA_LABELS,
- false)));
- return new SafetyLabels(dataLabels);
+ .createFromHrElement(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_DATA_LABELS,
+ requiredHrEles));
+ SecurityLabels securityLabels =
+ new SecurityLabelsFactory()
+ .createFromHrElement(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_SECURITY_LABELS,
+ requiredHrEles));
+ ThirdPartyVerification thirdPartyVerification =
+ new ThirdPartyVerificationFactory()
+ .createFromHrElement(
+ XmlUtils.getSingleChildElement(
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION,
+ requiredHrEles));
+ return new SafetyLabels(dataLabels, securityLabels, thirdPartyVerification);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+ public SafetyLabels createFromOdElement(Element safetyLabelsEle, long version)
+ throws MalformedXmlException {
if (safetyLabelsEle == null) {
- AslgenUtil.logI("No SafetyLabels found in od format.");
return null;
}
+ XmlUtils.throwIfExtraneousChildrenOd(
+ safetyLabelsEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+ var requiredOdEles = XmlUtils.getMostRecentVersion(mRequiredOdEles, version);
+
DataLabels dataLabels =
new DataLabelsFactory()
- .createFromOdElements(
- XmlUtils.listOf(
- XmlUtils.getOdPbundleWithName(
- safetyLabelsEle,
- XmlUtils.OD_NAME_DATA_LABELS,
- false)));
-
- return new SafetyLabels(dataLabels);
+ .createFromOdElement(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_DATA_LABELS,
+ requiredOdEles));
+ SecurityLabels securityLabels =
+ new SecurityLabelsFactory()
+ .createFromOdElement(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_SECURITY_LABELS,
+ requiredOdEles));
+ ThirdPartyVerification thirdPartyVerification =
+ new ThirdPartyVerificationFactory()
+ .createFromOdElement(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION,
+ requiredOdEles));
+ return new SafetyLabels(dataLabels, securityLabels, thirdPartyVerification);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
index 48643ba..ccb8445 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -21,8 +21,6 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
/** Security Labels representation */
public class SecurityLabels implements AslMarshallable {
@@ -35,8 +33,7 @@
}
/** Creates an on-device DOM element from the {@link SecurityLabels}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element ele = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SECURITY_LABELS);
if (mIsDataDeletable != null) {
ele.appendChild(
@@ -48,12 +45,11 @@
XmlUtils.createOdBooleanEle(
doc, XmlUtils.OD_NAME_IS_DATA_ENCRYPTED, mIsDataEncrypted));
}
- return XmlUtils.listOf(ele);
+ return ele;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element ele = doc.createElement(XmlUtils.HR_TAG_SECURITY_LABELS);
if (mIsDataDeletable != null) {
ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_DELETABLE, String.valueOf(mIsDataDeletable));
@@ -61,6 +57,6 @@
if (mIsDataEncrypted != null) {
ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, String.valueOf(mIsDataEncrypted));
}
- return XmlUtils.listOf(ele);
+ return ele;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
index 525a803..da98a42 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -22,15 +22,10 @@
import org.w3c.dom.Element;
-import java.util.List;
-
public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLabels> {
/** Creates a {@link SecurityLabels} from the human-readable DOM element. */
- @Override
- public SecurityLabels createFromHrElements(List<Element> elements)
- throws MalformedXmlException {
- Element ele = XmlUtils.getSingleElement(elements);
+ public SecurityLabels createFromHrElement(Element ele) throws MalformedXmlException {
if (ele == null) {
AslgenUtil.logI("No SecurityLabels found in hr format.");
return null;
@@ -43,10 +38,7 @@
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public SecurityLabels createFromOdElements(List<Element> elements)
- throws MalformedXmlException {
- Element ele = XmlUtils.getSingleElement(elements);
+ public SecurityLabels createFromOdElement(Element ele) throws MalformedXmlException {
if (ele == null) {
AslgenUtil.logI("No SecurityLabels found in od format.");
return null;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 242e7be..10d6e1a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -21,34 +21,43 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
public class SystemAppSafetyLabel implements AslMarshallable {
+ private final String mUrl;
private final Boolean mDeclaration;
- public SystemAppSafetyLabel(Boolean d) {
+ public SystemAppSafetyLabel(String url, Boolean d) {
this.mDeclaration = d;
+ this.mUrl = null;
}
/** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element systemAppSafetyLabelEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL);
- systemAppSafetyLabelEle.appendChild(
- XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration));
- return XmlUtils.listOf(systemAppSafetyLabelEle);
+ if (mUrl != null) {
+ systemAppSafetyLabelEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+ }
+ if (mDeclaration != null) {
+ systemAppSafetyLabelEle.appendChild(
+ XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration));
+ }
+ return systemAppSafetyLabelEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element systemAppSafetyLabelEle =
doc.createElement(XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL);
- XmlUtils.maybeSetHrBoolAttr(
- systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, mDeclaration);
- return XmlUtils.listOf(systemAppSafetyLabelEle);
+ if (mUrl != null) {
+ systemAppSafetyLabelEle.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
+ }
+ if (mDeclaration != null) {
+ systemAppSafetyLabelEle.setAttribute(
+ XmlUtils.HR_ATTR_DECLARATION, String.valueOf(mDeclaration));
+ }
+ return systemAppSafetyLabelEle;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index 7f4aa7a..971ae92 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -16,42 +16,77 @@
package com.android.asllib.marshallable;
-import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<SystemAppSafetyLabel> {
+ private final Map<Long, Set<String>> mRecognizedHrAttrs =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.HR_ATTR_URL)),
+ Map.entry(2L, Set.of(XmlUtils.HR_ATTR_DECLARATION)));
+ private final Map<Long, Set<String>> mRequiredHrAttrs =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.HR_ATTR_URL)),
+ Map.entry(2L, Set.of(XmlUtils.HR_ATTR_DECLARATION)));
+ private final Map<Long, Set<String>> mRecognizedHrEles = Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRecognizedOdEleNames =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.OD_NAME_URL)),
+ Map.entry(2L, Set.of(XmlUtils.OD_NAME_DECLARATION)));
+ private final Map<Long, Set<String>> mRequiredOdEleNames =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.OD_NAME_URL)),
+ Map.entry(2L, Set.of(XmlUtils.OD_NAME_DECLARATION)));
/** Creates a {@link SystemAppSafetyLabel} from the human-readable DOM element. */
- @Override
- public SystemAppSafetyLabel createFromHrElements(List<Element> elements)
+ public SystemAppSafetyLabel createFromHrElement(Element systemAppSafetyLabelEle, long version)
throws MalformedXmlException {
- Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
if (systemAppSafetyLabelEle == null) {
- AslgenUtil.logI("No SystemAppSafetyLabel found in hr format.");
return null;
}
+ XmlUtils.throwIfExtraneousAttributes(
+ systemAppSafetyLabelEle,
+ XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+ XmlUtils.throwIfExtraneousChildrenHr(
+ systemAppSafetyLabelEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+ String url =
+ XmlUtils.getStringAttr(
+ systemAppSafetyLabelEle,
+ XmlUtils.HR_ATTR_URL,
+ XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version));
Boolean declaration =
- XmlUtils.getBoolAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, true);
- return new SystemAppSafetyLabel(declaration);
+ XmlUtils.getBoolAttr(
+ systemAppSafetyLabelEle,
+ XmlUtils.HR_ATTR_DECLARATION,
+ XmlUtils.getMostRecentVersion(mRequiredHrAttrs, version));
+ return new SystemAppSafetyLabel(url, declaration);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public SystemAppSafetyLabel createFromOdElements(List<Element> elements)
+ public SystemAppSafetyLabel createFromOdElement(Element systemAppSafetyLabelEle, long version)
throws MalformedXmlException {
- Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
if (systemAppSafetyLabelEle == null) {
- AslgenUtil.logI("No SystemAppSafetyLabel found in od format.");
return null;
}
+ XmlUtils.throwIfExtraneousChildrenOd(
+ systemAppSafetyLabelEle,
+ XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+ String url =
+ XmlUtils.getOdStringEle(
+ systemAppSafetyLabelEle,
+ XmlUtils.OD_NAME_URL,
+ XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
Boolean declaration =
- XmlUtils.getOdBoolEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_DECLARATION, true);
- return new SystemAppSafetyLabel(declaration);
+ XmlUtils.getOdBoolEle(
+ systemAppSafetyLabelEle,
+ XmlUtils.OD_NAME_DECLARATION,
+ XmlUtils.getMostRecentVersion(mRequiredOdEleNames, version));
+ return new SystemAppSafetyLabel(url, declaration);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
index d74f3f0..151cdb1 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -21,8 +21,6 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
/** ThirdPartyVerification representation. */
public class ThirdPartyVerification implements AslMarshallable {
@@ -33,19 +31,17 @@
}
/** Creates an on-device DOM element from the {@link ThirdPartyVerification}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element ele =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION);
ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
- return XmlUtils.listOf(ele);
+ return ele;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element ele = doc.createElement(XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION);
ele.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
- return XmlUtils.listOf(ele);
+ return ele;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
index 197e7aa..f229ad6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -22,16 +22,11 @@
import org.w3c.dom.Element;
-import java.util.List;
-
public class ThirdPartyVerificationFactory
implements AslMarshallableFactory<ThirdPartyVerification> {
/** Creates a {@link ThirdPartyVerification} from the human-readable DOM element. */
- @Override
- public ThirdPartyVerification createFromHrElements(List<Element> elements)
- throws MalformedXmlException {
- Element ele = XmlUtils.getSingleElement(elements);
+ public ThirdPartyVerification createFromHrElement(Element ele) throws MalformedXmlException {
if (ele == null) {
AslgenUtil.logI("No ThirdPartyVerification found in hr format.");
return null;
@@ -42,10 +37,7 @@
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public ThirdPartyVerification createFromOdElements(List<Element> elements)
- throws MalformedXmlException {
- Element ele = XmlUtils.getSingleElement(elements);
+ public ThirdPartyVerification createFromOdElement(Element ele) throws MalformedXmlException {
if (ele == null) {
AslgenUtil.logI("No ThirdPartyVerification found in od format.");
return null;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index 9f789f0..f24e6bf 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -21,39 +21,39 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.util.List;
-
/** TransparencyInfo representation containing {@link AppInfo} */
public class TransparencyInfo implements AslMarshallable {
+
+ private final DeveloperInfo mDeveloperInfo;
private final AppInfo mAppInfo;
- public TransparencyInfo(AppInfo appInfo) {
+ public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) {
+ this.mDeveloperInfo = developerInfo;
this.mAppInfo = appInfo;
}
- /** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */
- public AppInfo getAppInfo() {
- return mAppInfo;
- }
-
/** Creates an on-device DOM element from the {@link TransparencyInfo}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
+ public Element toOdDomElement(Document doc) {
Element transparencyInfoEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO);
- if (mAppInfo != null) {
- XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc));
+ if (mDeveloperInfo != null) {
+ transparencyInfoEle.appendChild(mDeveloperInfo.toOdDomElement(doc));
}
- return XmlUtils.listOf(transparencyInfoEle);
+ if (mAppInfo != null) {
+ transparencyInfoEle.appendChild(mAppInfo.toOdDomElement(doc));
+ }
+ return transparencyInfoEle;
}
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
+ public Element toHrDomElement(Document doc) {
Element transparencyInfoEle = doc.createElement(XmlUtils.HR_TAG_TRANSPARENCY_INFO);
- if (mAppInfo != null) {
- XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toHrDomElements(doc));
+ if (mDeveloperInfo != null) {
+ transparencyInfoEle.appendChild(mDeveloperInfo.toHrDomElement(doc));
}
- return XmlUtils.listOf(transparencyInfoEle);
+ if (mAppInfo != null) {
+ transparencyInfoEle.appendChild(mAppInfo.toHrDomElement(doc));
+ }
+ return transparencyInfoEle;
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 40f2872..9e98941 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -16,47 +16,84 @@
package com.android.asllib.marshallable;
-import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class TransparencyInfoFactory implements AslMarshallableFactory<TransparencyInfo> {
+ private final Map<Long, Set<String>> mRecognizedHrAttrs =
+ Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRequiredHrAttrs = Map.ofEntries(Map.entry(1L, Set.of()));
+ private final Map<Long, Set<String>> mRecognizedHrEles =
+ Map.ofEntries(
+ Map.entry(1L, Set.of(XmlUtils.HR_TAG_DEVELOPER_INFO, XmlUtils.HR_TAG_APP_INFO)),
+ Map.entry(2L, Set.of(XmlUtils.HR_TAG_APP_INFO)));
+ private final Map<Long, Set<String>> mRequiredHrEles =
+ Map.ofEntries(Map.entry(1L, Set.of()), Map.entry(2L, Set.of(XmlUtils.HR_TAG_APP_INFO)));
+ private final Map<Long, Set<String>> mRecognizedOdEleNames =
+ Map.ofEntries(
+ Map.entry(
+ 1L, Set.of(XmlUtils.OD_NAME_DEVELOPER_INFO, XmlUtils.OD_NAME_APP_INFO)),
+ Map.entry(2L, Set.of(XmlUtils.OD_NAME_APP_INFO)));
+ private final Map<Long, Set<String>> mRequiredOdEles =
+ Map.ofEntries(
+ Map.entry(1L, Set.of()), Map.entry(2L, Set.of(XmlUtils.OD_NAME_APP_INFO)));
/** Creates a {@link TransparencyInfo} from the human-readable DOM element. */
- @Override
- public TransparencyInfo createFromHrElements(List<Element> elements)
+ public TransparencyInfo createFromHrElement(Element transparencyInfoEle, long version)
throws MalformedXmlException {
- Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
if (transparencyInfoEle == null) {
- AslgenUtil.logI("No TransparencyInfo found in hr format.");
return null;
}
+ XmlUtils.throwIfExtraneousAttributes(
+ transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrAttrs, version));
+ XmlUtils.throwIfExtraneousChildrenHr(
+ transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedHrEles, version));
+ Element developerInfoEle =
+ XmlUtils.getSingleChildElement(
+ transparencyInfoEle,
+ XmlUtils.HR_TAG_DEVELOPER_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
Element appInfoEle =
- XmlUtils.getSingleChildElement(transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, true);
- AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle));
+ XmlUtils.getSingleChildElement(
+ transparencyInfoEle,
+ XmlUtils.HR_TAG_APP_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredHrEles, version));
+ AppInfo appInfo = new AppInfoFactory().createFromHrElement(appInfoEle, version);
- return new TransparencyInfo(appInfo);
+ return new TransparencyInfo(developerInfo, appInfo);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public TransparencyInfo createFromOdElements(List<Element> elements)
+ public TransparencyInfo createFromOdElement(Element transparencyInfoEle, long version)
throws MalformedXmlException {
- Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
if (transparencyInfoEle == null) {
- AslgenUtil.logI("No TransparencyInfo found in od format.");
return null;
}
+ XmlUtils.throwIfExtraneousChildrenOd(
+ transparencyInfoEle, XmlUtils.getMostRecentVersion(mRecognizedOdEleNames, version));
+ Element developerInfoEle =
+ XmlUtils.getOdPbundleWithName(
+ transparencyInfoEle,
+ XmlUtils.OD_NAME_DEVELOPER_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredOdEles, version));
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
Element appInfoEle =
- XmlUtils.getOdPbundleWithName(transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, true);
- AppInfo appInfo = new AppInfoFactory().createFromOdElements(XmlUtils.listOf(appInfoEle));
+ XmlUtils.getOdPbundleWithName(
+ transparencyInfoEle,
+ XmlUtils.OD_NAME_APP_INFO,
+ XmlUtils.getMostRecentVersion(mRequiredOdEles, version));
+ AppInfo appInfo = new AppInfoFactory().createFromOdElement(appInfoEle, version);
- return new TransparencyInfo(appInfo);
+ return new TransparencyInfo(developerInfo, appInfo);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 2c1517b..52c4390 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -25,6 +25,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
public class XmlUtils {
@@ -70,6 +72,8 @@
public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting";
public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting";
public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy";
+ public static final String HR_ATTR_DEVELOPER_ID = "developerId";
+ public static final String HR_ATTR_APPLICATION_ID = "applicationId";
public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints";
public static final String HR_TAG_FIRST_PARTY_ENDPOINTS = "first-party-endpoints";
public static final String HR_TAG_SERVICE_PROVIDER_ENDPOINTS = "service-provider-endpoints";
@@ -102,10 +106,12 @@
public static final String OD_NAME_CONTAINS_ADS = "contains_ads";
public static final String OD_NAME_OBEY_APS = "obey_aps";
public static final String OD_NAME_APS_COMPLIANT = "aps_compliant";
+ public static final String OD_NAME_DEVELOPER_ID = "developer_id";
+ public static final String OD_NAME_APPLICATION_ID = "application_id";
public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting";
public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting";
public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy";
- public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoints";
+ public static final String OD_NAME_SECURITY_ENDPOINTS = "security_endpoints";
public static final String OD_NAME_FIRST_PARTY_ENDPOINTS = "first_party_endpoints";
public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINTS = "service_provider_endpoints";
public static final String OD_NAME_CATEGORY = "category";
@@ -140,6 +146,15 @@
/**
* Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
*/
+ public static Element getSingleChildElement(
+ Node parentEle, String tagName, Set<String> requiredStrings)
+ throws MalformedXmlException {
+ return getSingleChildElement(parentEle, tagName, requiredStrings.contains(tagName));
+ }
+
+ /**
+ * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+ */
public static Element getSingleChildElement(Node parentEle, String tagName, boolean required)
throws MalformedXmlException {
String parentTagNameForErrorMsg =
@@ -287,19 +302,36 @@
}
/** Gets a pipeline-split attribute. */
+ public static List<String> getPipelineSplitAttr(
+ Element ele, String attrName, Set<String> requiredNames) throws MalformedXmlException {
+ return getPipelineSplitAttr(ele, attrName, requiredNames.contains(attrName));
+ }
+
+ /** Gets a pipeline-split attribute. */
public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required)
throws MalformedXmlException {
List<String> list =
Arrays.stream(ele.getAttribute(attrName).split("\\|")).collect(Collectors.toList());
- if ((list.isEmpty() || list.get(0).isEmpty()) && required) {
- throw new MalformedXmlException(
- String.format(
- "Delimited string %s was required but missing, in %s.",
- attrName, ele.getTagName()));
+ if ((list.isEmpty() || list.get(0).isEmpty())) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Delimited string %s was required but missing, in %s.",
+ attrName, ele.getTagName()));
+ }
+ return null;
}
return list;
}
+ /**
+ * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+ */
+ public static Boolean getBoolAttr(Element ele, String attrName, Set<String> requiredStrings)
+ throws MalformedXmlException {
+ return getBoolAttr(ele, attrName, requiredStrings.contains(attrName));
+ }
+
/** Gets a Boolean attribute. */
public static Boolean getBoolAttr(Element ele, String attrName, boolean required)
throws MalformedXmlException {
@@ -314,6 +346,12 @@
}
/** Gets a Boolean attribute. */
+ public static Boolean getOdBoolEle(Element ele, String nameName, Set<String> requiredNames)
+ throws MalformedXmlException {
+ return getOdBoolEle(ele, nameName, requiredNames.contains(nameName));
+ }
+
+ /** Gets a Boolean attribute. */
public static Boolean getOdBoolEle(Element ele, String nameName, boolean required)
throws MalformedXmlException {
List<Element> boolEles =
@@ -376,6 +414,12 @@
}
/** Gets an on-device String attribute. */
+ public static String getOdStringEle(Element ele, String nameName, Set<String> requiredNames)
+ throws MalformedXmlException {
+ return getOdStringEle(ele, nameName, requiredNames.contains(nameName));
+ }
+
+ /** Gets an on-device String attribute. */
public static String getOdStringEle(Element ele, String nameName, boolean required)
throws MalformedXmlException {
List<Element> eles =
@@ -404,6 +448,13 @@
}
/** Gets a OD Pbundle Element attribute with the specified name. */
+ public static Element getOdPbundleWithName(
+ Element ele, String nameName, Set<String> requiredStrings)
+ throws MalformedXmlException {
+ return getOdPbundleWithName(ele, nameName, requiredStrings.contains(nameName));
+ }
+
+ /** Gets a OD Pbundle Element attribute with the specified name. */
public static Element getOdPbundleWithName(Element ele, String nameName, boolean required)
throws MalformedXmlException {
List<Element> eles =
@@ -425,6 +476,14 @@
return eles.get(0);
}
+ /**
+ * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+ */
+ public static String getStringAttr(Element ele, String attrName, Set<String> requiredStrings)
+ throws MalformedXmlException {
+ return getStringAttr(ele, attrName, requiredStrings.contains(attrName));
+ }
+
/** Gets a required String attribute. */
public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
return getStringAttr(ele, attrName, true);
@@ -476,6 +535,13 @@
/** Gets human-readable style String array. */
public static List<String> getHrItemsAsStrings(
+ Element parent, String elementName, Set<String> requiredNames)
+ throws MalformedXmlException {
+ return getHrItemsAsStrings(parent, elementName, requiredNames.contains(elementName));
+ }
+
+ /** Gets human-readable style String array. */
+ public static List<String> getHrItemsAsStrings(
Element parent, String elementName, boolean required) throws MalformedXmlException {
List<Element> arrayEles = XmlUtils.getChildrenByTagName(parent, elementName);
@@ -501,6 +567,12 @@
}
/** Gets on-device style String array. */
+ public static List<String> getOdStringArray(
+ Element ele, String nameName, Set<String> requiredNames) throws MalformedXmlException {
+ return getOdStringArray(ele, nameName, requiredNames.contains(nameName));
+ }
+
+ /** Gets on-device style String array. */
public static List<String> getOdStringArray(Element ele, String nameName, boolean required)
throws MalformedXmlException {
List<Element> arrayEles =
@@ -530,6 +602,73 @@
return strs;
}
+ /** Throws if extraneous child elements detected */
+ public static void throwIfExtraneousChildrenHr(Element ele, Set<String> expectedChildNames)
+ throws MalformedXmlException {
+ var childEles = XmlUtils.asElementList(ele.getChildNodes());
+ List<Element> extraneousEles =
+ childEles.stream()
+ .filter(e -> !expectedChildNames.contains(e.getTagName()))
+ .collect(Collectors.toList());
+ if (!extraneousEles.isEmpty()) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unexpected element(s) %s in %s.",
+ extraneousEles.stream()
+ .map(Element::getTagName)
+ .collect(Collectors.joining(",")),
+ ele.getTagName()));
+ }
+ }
+
+ /** Throws if extraneous child elements detected */
+ public static void throwIfExtraneousChildrenOd(Element ele, Set<String> expectedChildNames)
+ throws MalformedXmlException {
+ var allChildElements = XmlUtils.asElementList(ele.getChildNodes());
+ List<Element> extraneousEles =
+ allChildElements.stream()
+ .filter(
+ e ->
+ !e.getAttribute(XmlUtils.OD_ATTR_NAME).isEmpty()
+ && !expectedChildNames.contains(
+ e.getAttribute(XmlUtils.OD_ATTR_NAME)))
+ .collect(Collectors.toList());
+ if (!extraneousEles.isEmpty()) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unexpected element(s) in %s: %s",
+ ele.getTagName(),
+ extraneousEles.stream()
+ .map(
+ e ->
+ String.format(
+ "%s name=%s",
+ e.getTagName(),
+ e.getAttribute(XmlUtils.OD_ATTR_NAME)))
+ .collect(Collectors.joining(","))));
+ }
+ }
+
+ /** Throws if extraneous attributes detected */
+ public static void throwIfExtraneousAttributes(Element ele, Set<String> expectedAttrNames)
+ throws MalformedXmlException {
+ var attrs = ele.getAttributes();
+ List<String> attrNames = new ArrayList<>();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ attrNames.add(attrs.item(i).getNodeName());
+ }
+ List<String> extraneousAttrs =
+ attrNames.stream()
+ .filter(s -> !expectedAttrNames.contains(s))
+ .collect(Collectors.toList());
+ if (!extraneousAttrs.isEmpty()) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unexpected attr(s) %s in %s.",
+ String.join(",", extraneousAttrs), ele.getTagName()));
+ }
+ }
+
/**
* Utility method for making a List from one element, to support easier refactoring if needed.
* For example, List.of() doesn't support null elements.
@@ -537,4 +676,26 @@
public static List<Element> listOf(Element e) {
return Arrays.asList(e);
}
+
+ /**
+ * Gets the most recent version of fields in the mapping. This way when a new version is
+ * released, we only need to update the mappings that were modified. The rest will fall back to
+ * the most recent previous version.
+ */
+ public static Set<String> getMostRecentVersion(
+ Map<Long, Set<String>> versionToFieldsMapping, long version)
+ throws MalformedXmlException {
+ long bestVersion = 0;
+ Set<String> bestSet = null;
+ for (Map.Entry<Long, Set<String>> entry : versionToFieldsMapping.entrySet()) {
+ if (entry.getKey() > bestVersion && entry.getKey() <= version) {
+ bestVersion = entry.getKey();
+ bestSet = entry.getValue();
+ }
+ }
+ if (bestSet == null) {
+ throw new MalformedXmlException("Unexpected version: " + version);
+ }
+ return bestSet;
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index 5d1d45a..262f76d 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -41,6 +41,18 @@
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
+ @Test
+ public void testValidOd() throws Exception {
+ System.out.println("start testing valid od.");
+ Path odPath = Paths.get(VALID_MAPPINGS_PATH, "general-v1", OD_XML_FILENAME);
+ InputStream odStream = getClass().getClassLoader().getResourceAsStream(odPath.toString());
+ String odContents =
+ TestUtils.getFormattedXml(
+ new String(odStream.readAllBytes(), StandardCharsets.UTF_8), false);
+ AndroidSafetyLabel unusedAsl =
+ AslConverter.readFromString(odContents, AslConverter.Format.ON_DEVICE);
+ }
+
/** Tests valid mappings between HR and OD. */
@Test
public void testValidMappings() throws Exception {
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
index 61a7823..283ccbc 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
@@ -16,12 +16,18 @@
package com.android.asllib.marshallable;
+import static org.junit.Assert.assertThrows;
+
import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+
+import java.nio.file.Paths;
@RunWith(JUnit4.class)
public class AndroidSafetyLabelTest {
@@ -66,47 +72,49 @@
testOdToHrAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
}
- /** Test for android safety label with system app safety label. */
- @Test
- public void testAndroidSafetyLabelWithSystemAppSafetyLabel() throws Exception {
- System.out.println("starting testAndroidSafetyLabelWithSystemAppSafetyLabel.");
- testHrToOdAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
- testOdToHrAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
- }
-
- /** Test for android safety label with transparency info. */
- @Test
- public void testAndroidSafetyLabelWithTransparencyInfo() throws Exception {
- System.out.println("starting testAndroidSafetyLabelWithTransparencyInfo.");
- testHrToOdAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
- testOdToHrAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
- }
-
private void hrToOdExpectException(String fileName) {
- TestUtils.hrToOdExpectException(
- new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_HR_PATH, fileName);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> {
+ new AndroidSafetyLabelFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName)));
+ });
}
private void odToHrExpectException(String fileName) {
- TestUtils.odToHrExpectException(
- new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_OD_PATH, fileName);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> {
+ new AndroidSafetyLabelFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName)));
+ });
}
private void testHrToOdAndroidSafetyLabel(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new AndroidSafetyLabelFactory(),
- ANDROID_SAFETY_LABEL_HR_PATH,
- ANDROID_SAFETY_LABEL_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ AndroidSafetyLabel asl =
+ new AndroidSafetyLabelFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName)));
+ Element aslEle = asl.toOdDomElement(doc);
+ doc.appendChild(aslEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName));
}
private void testOdToHrAndroidSafetyLabel(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new AndroidSafetyLabelFactory(),
- ANDROID_SAFETY_LABEL_OD_PATH,
- ANDROID_SAFETY_LABEL_HR_PATH,
- fileName);
+ var doc = TestUtils.document();
+ AndroidSafetyLabel asl =
+ new AndroidSafetyLabelFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(ANDROID_SAFETY_LABEL_OD_PATH, fileName)));
+ Element aslEle = asl.toHrDomElement(doc);
+ doc.appendChild(aslEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(ANDROID_SAFETY_LABEL_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index d823c48..7806061 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -26,12 +26,14 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
import java.nio.file.Paths;
import java.util.List;
@RunWith(JUnit4.class)
public class AppInfoTest {
+ private static final long DEFAULT_VERSION = 2L;
private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr";
private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od";
public static final List<String> REQUIRED_FIELD_NAMES =
@@ -45,7 +47,24 @@
public static final List<String> OPTIONAL_FIELD_NAMES = List.of();
public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of();
+ public static final List<String> REQUIRED_FIELD_NAMES_OD_V1 =
+ List.of(
+ "title",
+ "description",
+ "contains_ads",
+ "obey_aps",
+ "ads_fingerprinting",
+ "security_fingerprinting",
+ "privacy_policy",
+ "security_endpoints",
+ "first_party_endpoints",
+ "service_provider_endpoints",
+ "category");
+ public static final List<String> OPTIONAL_FIELD_NAMES_OD_V1 = List.of("website", "email");
+
private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+ private static final String ALL_FIELDS_VALID_V1_FILE_NAME = "all-fields-valid-v1.xml";
+ public static final String UNRECOGNIZED_V1_FILE_NAME = "unrecognized-v1.xml";
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@@ -63,6 +82,61 @@
testOdToHrAppInfo(ALL_FIELDS_VALID_FILE_NAME);
}
+ /** Test for all fields valid v1. */
+ @Test
+ public void testAllFieldsValidV1() throws Exception {
+ System.out.println("starting testAllFieldsValidV1.");
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+ 1L);
+ }
+
+ /** Test for unrecognized field v1. */
+ @Test
+ public void testUnrecognizedFieldV1() throws Exception {
+ System.out.println("starting testUnrecognizedFieldV1.");
+ assertThrows(
+ MalformedXmlException.class,
+ () ->
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(
+ APP_INFO_OD_PATH,
+ UNRECOGNIZED_V1_FILE_NAME)),
+ 1L));
+ }
+
+ /** Tests missing required fields fails, V1. */
+ @Test
+ public void testMissingRequiredFieldsOdV1() throws Exception {
+ for (String reqField : REQUIRED_FIELD_NAMES_OD_V1) {
+ System.out.println("testing missing required field od v1: " + reqField);
+ var appInfoEle =
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromOdElement(appInfoEle, 1L));
+ }
+ }
+
+ /** Tests missing optional fields passes, V1. */
+ @Test
+ public void testMissingOptionalFieldsOdV1() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES_OD_V1) {
+ System.out.println("testing missing optional field od v1: " + optField);
+ var appInfoEle =
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(appInfoEle, optField);
+ new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+ }
+ }
+
/** Tests missing required fields fails. */
@Test
public void testMissingRequiredFields() throws Exception {
@@ -70,24 +144,24 @@
for (String reqField : REQUIRED_FIELD_NAMES) {
System.out.println("testing missing required field hr: " + reqField);
var appInfoEle =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- appInfoEle.get(0).removeAttribute(reqField);
+ appInfoEle.removeAttribute(reqField);
assertThrows(
MalformedXmlException.class,
- () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+ () -> new AppInfoFactory().createFromHrElement(appInfoEle, DEFAULT_VERSION));
}
for (String reqField : REQUIRED_FIELD_NAMES_OD) {
System.out.println("testing missing required field od: " + reqField);
var appInfoEle =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+ TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
assertThrows(
MalformedXmlException.class,
- () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+ () -> new AppInfoFactory().createFromOdElement(appInfoEle, DEFAULT_VERSION));
}
}
@@ -98,24 +172,24 @@
for (String reqChildName : REQUIRED_CHILD_NAMES) {
System.out.println("testing missing required child hr: " + reqChildName);
var appInfoEle =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- var child = XmlUtils.getChildrenByTagName(appInfoEle.get(0), reqChildName).get(0);
- appInfoEle.get(0).removeChild(child);
+ var child = XmlUtils.getChildrenByTagName(appInfoEle, reqChildName).get(0);
+ appInfoEle.removeChild(child);
assertThrows(
MalformedXmlException.class,
- () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+ () -> new AppInfoFactory().createFromHrElement(appInfoEle, DEFAULT_VERSION));
}
for (String reqField : REQUIRED_CHILD_NAMES_OD) {
System.out.println("testing missing required child od: " + reqField);
var appInfoEle =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+ TestUtils.removeOdChildEleWithName(appInfoEle, reqField);
assertThrows(
MalformedXmlException.class,
- () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+ () -> new AppInfoFactory().createFromOdElement(appInfoEle, DEFAULT_VERSION));
}
}
@@ -124,38 +198,51 @@
public void testMissingOptionalFields() throws Exception {
for (String optField : OPTIONAL_FIELD_NAMES) {
var ele =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- ele.get(0).removeAttribute(optField);
- AppInfo appInfo = new AppInfoFactory().createFromHrElements(ele);
- appInfo.toOdDomElements(TestUtils.document());
+ ele.removeAttribute(optField);
+ AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
+ appInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
var ele =
- TestUtils.getElementsFromResource(
+ TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(ele.get(0), optField);
- AppInfo appInfo = new AppInfoFactory().createFromOdElements(ele);
- appInfo.toHrDomElements(TestUtils.document());
+ TestUtils.removeOdChildEleWithName(ele, optField);
+ AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
+ appInfo.toHrDomElement(TestUtils.document());
}
}
private void testHrToOdAppInfo(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new AppInfoFactory(),
- APP_INFO_HR_PATH,
- APP_INFO_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ AppInfo appInfo =
+ new AppInfoFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_HR_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element appInfoEle = appInfo.toOdDomElement(doc);
+ doc.appendChild(appInfoEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(APP_INFO_OD_PATH, fileName));
}
+
private void testOdToHrAppInfo(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new AppInfoFactory(),
- APP_INFO_OD_PATH,
- APP_INFO_HR_PATH,
- fileName);
+ testOdToHrAppInfo(fileName, DEFAULT_VERSION);
+ }
+
+ private void testOdToHrAppInfo(String fileName, long version) throws Exception {
+ var doc = TestUtils.document();
+ AppInfo appInfo =
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, fileName)),
+ version);
+ Element appInfoEle = appInfo.toHrDomElement(doc);
+ doc.appendChild(appInfoEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(APP_INFO_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index ff43741..b557fea 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -16,15 +16,27 @@
package com.android.asllib.marshallable;
+import static org.junit.Assert.assertThrows;
+
import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
@RunWith(JUnit4.class)
public class DataLabelsTest {
+ private static final long DEFAULT_VERSION = 2L;
+
private static final String DATA_LABELS_HR_PATH = "com/android/asllib/datalabels/hr";
private static final String DATA_LABELS_OD_PATH = "com/android/asllib/datalabels/od";
@@ -297,29 +309,43 @@
odToHrExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
}
- private void hrToOdExpectException(String fileName) {
- TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
+ private void hrToOdExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var ele = TestUtils.getElementFromResource(Paths.get(DATA_LABELS_HR_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new DataLabelsFactory().createFromHrElement(ele));
}
- private void odToHrExpectException(String fileName) {
- TestUtils.odToHrExpectException(new DataLabelsFactory(), DATA_LABELS_OD_PATH, fileName);
+ private void odToHrExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var ele = TestUtils.getElementFromResource(Paths.get(DATA_LABELS_OD_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new DataLabelsFactory().createFromOdElement(ele));
}
private void testHrToOdDataLabels(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new DataLabelsFactory(),
- DATA_LABELS_HR_PATH,
- DATA_LABELS_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ DataLabels dataLabels =
+ new DataLabelsFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(DATA_LABELS_HR_PATH, fileName)));
+ Element resultingEle = dataLabels.toOdDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(DATA_LABELS_OD_PATH, fileName));
}
private void testOdToHrDataLabels(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new DataLabelsFactory(),
- DATA_LABELS_OD_PATH,
- DATA_LABELS_HR_PATH,
- fileName);
+ var doc = TestUtils.document();
+ DataLabels dataLabels =
+ new DataLabelsFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(DATA_LABELS_OD_PATH, fileName)));
+ Element resultingEle = dataLabels.toHrDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(DATA_LABELS_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index 19d1626..7cd510f 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -16,15 +16,27 @@
package com.android.asllib.marshallable;
+import static org.junit.Assert.assertThrows;
+
import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
@RunWith(JUnit4.class)
public class SafetyLabelsTest {
+ private static final long DEFAULT_VERSION = 2L;
+
private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr";
private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od";
@@ -52,29 +64,51 @@
testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
}
- private void hrToOdExpectException(String fileName) {
- TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
+ private void hrToOdExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var safetyLabelsEle =
+ TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_HR_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () ->
+ new SafetyLabelsFactory()
+ .createFromHrElement(safetyLabelsEle, DEFAULT_VERSION));
}
- private void odToHrExpectException(String fileName) {
- TestUtils.odToHrExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_OD_PATH, fileName);
+ private void odToHrExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var safetyLabelsEle =
+ TestUtils.getElementFromResource(Paths.get(SAFETY_LABELS_OD_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () ->
+ new SafetyLabelsFactory()
+ .createFromOdElement(safetyLabelsEle, DEFAULT_VERSION));
}
private void testHrToOdSafetyLabels(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new SafetyLabelsFactory(),
- SAFETY_LABELS_HR_PATH,
- SAFETY_LABELS_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ SafetyLabels safetyLabels =
+ new SafetyLabelsFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(SAFETY_LABELS_HR_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element appInfoEle = safetyLabels.toOdDomElement(doc);
+ doc.appendChild(appInfoEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_OD_PATH, fileName));
}
private void testOdToHrSafetyLabels(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new SafetyLabelsFactory(),
- SAFETY_LABELS_OD_PATH,
- SAFETY_LABELS_HR_PATH,
- fileName);
+ var doc = TestUtils.document();
+ SafetyLabels safetyLabels =
+ new SafetyLabelsFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(SAFETY_LABELS_OD_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element appInfoEle = safetyLabels.toHrDomElement(doc);
+ doc.appendChild(appInfoEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(SAFETY_LABELS_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
index 87d3e44..9dcc652 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -16,15 +16,27 @@
package com.android.asllib.marshallable;
+import static org.junit.Assert.assertThrows;
+
import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.ParserConfigurationException;
@RunWith(JUnit4.class)
public class SystemAppSafetyLabelTest {
+ private static final long DEFAULT_VERSION = 2L;
+
private static final String SYSTEM_APP_SAFETY_LABEL_HR_PATH =
"com/android/asllib/systemappsafetylabel/hr";
private static final String SYSTEM_APP_SAFETY_LABEL_OD_PATH =
@@ -57,31 +69,49 @@
odToHrExpectException(MISSING_BOOL_FILE_NAME);
}
- private void hrToOdExpectException(String fileName) {
- TestUtils.hrToOdExpectException(
- new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName);
+ private void hrToOdExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var ele =
+ TestUtils.getElementFromResource(
+ Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new SystemAppSafetyLabelFactory().createFromHrElement(ele, DEFAULT_VERSION));
}
- private void odToHrExpectException(String fileName) {
- TestUtils.odToHrExpectException(
- new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName);
+ private void odToHrExpectException(String fileName)
+ throws ParserConfigurationException, IOException, SAXException {
+ var ele =
+ TestUtils.getElementFromResource(
+ Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName));
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new SystemAppSafetyLabelFactory().createFromOdElement(ele, DEFAULT_VERSION));
}
private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new SystemAppSafetyLabelFactory(),
- SYSTEM_APP_SAFETY_LABEL_HR_PATH,
- SYSTEM_APP_SAFETY_LABEL_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ SystemAppSafetyLabel systemAppSafetyLabel =
+ new SystemAppSafetyLabelFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element resultingEle = systemAppSafetyLabel.toOdDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName));
}
private void testOdToHrSystemAppSafetyLabel(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new SystemAppSafetyLabelFactory(),
- SYSTEM_APP_SAFETY_LABEL_OD_PATH,
- SYSTEM_APP_SAFETY_LABEL_HR_PATH,
- fileName);
+ var doc = TestUtils.document();
+ SystemAppSafetyLabel systemAppSafetyLabel =
+ new SystemAppSafetyLabelFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element resultingEle = systemAppSafetyLabel.toHrDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
index 8a0b35e..6547fb9 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -22,9 +22,14 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+
+import java.nio.file.Paths;
@RunWith(JUnit4.class)
public class TransparencyInfoTest {
+ private static final long DEFAULT_VERSION = 2L;
+
private static final String TRANSPARENCY_INFO_HR_PATH =
"com/android/asllib/transparencyinfo/hr";
private static final String TRANSPARENCY_INFO_OD_PATH =
@@ -45,20 +50,28 @@
}
private void testHrToOdTransparencyInfo(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new TransparencyInfoFactory(),
- TRANSPARENCY_INFO_HR_PATH,
- TRANSPARENCY_INFO_OD_PATH,
- fileName);
+ var doc = TestUtils.document();
+ TransparencyInfo transparencyInfo =
+ new TransparencyInfoFactory()
+ .createFromHrElement(
+ TestUtils.getElementFromResource(
+ Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element resultingEle = transparencyInfo.toOdDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName));
}
private void testOdToHrTransparencyInfo(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new TransparencyInfoFactory(),
- TRANSPARENCY_INFO_OD_PATH,
- TRANSPARENCY_INFO_HR_PATH,
- fileName);
+ var doc = TestUtils.document();
+ TransparencyInfo transparencyInfo =
+ new TransparencyInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(TRANSPARENCY_INFO_OD_PATH, fileName)),
+ DEFAULT_VERSION);
+ Element resultingEle = transparencyInfo.toHrDomElement(doc);
+ doc.appendChild(resultingEle);
+ TestUtils.testFormatToFormat(doc, Paths.get(TRANSPARENCY_INFO_HR_PATH, fileName));
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
index ea90993..f8ef40a 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -17,11 +17,8 @@
package com.android.asllib.testutils;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
import com.android.asllib.marshallable.AslMarshallable;
-import com.android.asllib.marshallable.AslMarshallableFactory;
-import com.android.asllib.util.MalformedXmlException;
import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
@@ -36,7 +33,6 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
@@ -60,7 +56,19 @@
}
/** Gets List of Element from a path to an existing Resource. */
- public static List<Element> getElementsFromResource(Path filePath)
+ public static Element getElementFromResource(Path filePath)
+ throws ParserConfigurationException, IOException, SAXException {
+ String str = readStrFromResource(filePath);
+ InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+ return document.getDocumentElement();
+ }
+
+ /** Gets List of Element from a path to an existing Resource. */
+ public static List<Element> getChildElementsFromResource(Path filePath)
throws ParserConfigurationException, IOException, SAXException {
String str = readStrFromResource(filePath);
InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
@@ -69,16 +77,13 @@
factory.setNamespaceAware(true);
Document document = factory.newDocumentBuilder().parse(stream);
Element root = document.getDocumentElement();
- if (root.getTagName().equals(HOLDER_TAG_NAME)) {
- String tagName =
- XmlUtils.asElementList(root.getChildNodes()).stream()
- .findFirst()
- .get()
- .getTagName();
- return XmlUtils.getChildrenByTagName(root, tagName);
- } else {
- return List.of(root);
- }
+
+ String tagName =
+ XmlUtils.asElementList(root.getChildNodes()).stream()
+ .findFirst()
+ .get()
+ .getTagName();
+ return XmlUtils.getChildrenByTagName(root, tagName);
}
/** Reads a Document into a String. */
@@ -130,86 +135,13 @@
return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
}
- /** Helper for testing human-readable to on-device conversion expecting exception */
- public static <T extends AslMarshallable> void hrToOdExpectException(
- AslMarshallableFactory<T> factory, String hrFolderPath, String fileName) {
- assertThrows(
- MalformedXmlException.class,
- () -> {
- factory.createFromHrElements(
- TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
- });
- }
-
- /** Helper for testing on-device to human-readable conversion expecting exception */
- public static <T extends AslMarshallable> void odToHrExpectException(
- AslMarshallableFactory<T> factory, String odFolderPath, String fileName) {
- assertThrows(
- MalformedXmlException.class,
- () -> {
- factory.createFromOdElements(
- TestUtils.getElementsFromResource(Paths.get(odFolderPath, fileName)));
- });
- }
-
- /** Helper for testing human-readable to on-device conversion */
- public static <T extends AslMarshallable> void testHrToOd(
- Document doc,
- AslMarshallableFactory<T> factory,
- String hrFolderPath,
- String odFolderPath,
- String fileName)
- throws Exception {
- testFormatToFormat(doc, factory, hrFolderPath, odFolderPath, fileName, true);
- }
-
- /** Helper for testing on-device to human-readable conversion */
- public static <T extends AslMarshallable> void testOdToHr(
- Document doc,
- AslMarshallableFactory<T> factory,
- String odFolderPath,
- String hrFolderPath,
- String fileName)
- throws Exception {
- testFormatToFormat(doc, factory, odFolderPath, hrFolderPath, fileName, false);
- }
-
/** Helper for testing format to format conversion */
- private static <T extends AslMarshallable> void testFormatToFormat(
- Document doc,
- AslMarshallableFactory<T> factory,
- String inFolderPath,
- String outFolderPath,
- String fileName,
- boolean hrToOd)
- throws Exception {
- AslMarshallable marshallable =
- hrToOd
- ? factory.createFromHrElements(
- TestUtils.getElementsFromResource(
- Paths.get(inFolderPath, fileName)))
- : factory.createFromOdElements(
- TestUtils.getElementsFromResource(
- Paths.get(inFolderPath, fileName)));
-
- List<Element> elements =
- hrToOd ? marshallable.toOdDomElements(doc) : marshallable.toHrDomElements(doc);
- if (elements.isEmpty()) {
- throw new IllegalStateException("elements was empty.");
- } else if (elements.size() == 1) {
- doc.appendChild(elements.get(0));
- } else {
- Element root = doc.createElement(TestUtils.HOLDER_TAG_NAME);
- for (var child : elements) {
- root.appendChild(child);
- }
- doc.appendChild(root);
- }
+ public static <T extends AslMarshallable> void testFormatToFormat(
+ Document doc, Path expectedOutPath) throws Exception {
String converted = TestUtils.getFormattedXml(TestUtils.docToStr(doc, true), true);
System.out.println("Converted: " + converted);
String expectedOutContents =
- TestUtils.getFormattedXml(
- TestUtils.readStrFromResource(Paths.get(outFolderPath, fileName)), true);
+ TestUtils.getFormattedXml(TestUtils.readStrFromResource(expectedOutPath), true);
System.out.println("Expected: " + expectedOutContents);
assertEquals(expectedOutContents, converted);
}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
index ec0cd70..7478e39 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
@@ -1,3 +1,17 @@
<app-metadata-bundles>
-
+ <system-app-safety-label declaration="true">
+ </system-app-safety-label>
+ <transparency-info>
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
+ </transparency-info>
</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
index 19bfd82..587c49a 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
@@ -1 +1,17 @@
-<app-metadata-bundles version="123456"></app-metadata-bundles>
\ No newline at end of file
+<app-metadata-bundles version="2">
+ <system-app-safety-label declaration="true">
+ </system-app-safety-label>
+ <transparency-info>
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
+ </transparency-info>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
index 03e71d2..9cfb8bc 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
@@ -1,4 +1,19 @@
-<app-metadata-bundles version="123456">
+<app-metadata-bundles version="2">
<safety-labels>
</safety-labels>
+ <system-app-safety-label declaration="true">
+ </system-app-safety-label>
+ <transparency-info>
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
+ </transparency-info>
</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
deleted file mode 100644
index afb0486..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<app-metadata-bundles version="123456">
-<system-app-safety-label declaration="true">
-</system-app-safety-label>
-</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
deleted file mode 100644
index a00ef65..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<app-metadata-bundles version="123456">
- <transparency-info>
- <app-info
- apsCompliant="false"
- privacyPolicy="www.example.com">
- <first-party-endpoints>
- <item>url1</item>
- </first-party-endpoints>
- <service-provider-endpoints>
- <item>url55</item>
- <item>url56</item>
- </service-provider-endpoints>
- </app-info>
- </transparency-info>
-</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
index 1aa3aa9..9adfa98 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
@@ -1,2 +1,20 @@
<bundle>
+ <pbundle_as_map name="system_app_safety_label">
+ <boolean name="declaration" value="true"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <boolean name="aps_compliant" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
index 37bdfad..7a4c82a 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
@@ -1,3 +1,21 @@
<bundle>
- <long name="version" value="123456"/>
+ <long name="version" value="2"/>
+ <pbundle_as_map name="system_app_safety_label">
+ <boolean name="declaration" value="true"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
index f00fb26..3a3e5d3 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
@@ -1,5 +1,22 @@
<bundle>
- <long name="version" value="123456"/>
- <pbundle_as_map name="safety_labels">
+ <long name="version" value="2"/>
+ <pbundle_as_map name="safety_labels"/>
+ <pbundle_as_map name="system_app_safety_label">
+ <boolean name="declaration" value="true"/>
</pbundle_as_map>
-</bundle>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
deleted file mode 100644
index e8640c4..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<bundle>
- <long name="version" value="123456"/>
- <pbundle_as_map name="system_app_safety_label">
- <boolean name="declaration" value="true"/>
- </pbundle_as_map>
-</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
deleted file mode 100644
index d0c8668..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<bundle>
- <long name="version" value="123456"/>
- <pbundle_as_map name="transparency_info">
- <pbundle_as_map name="app_info">
- <boolean name="aps_compliant" value="false"/>
- <string name="privacy_policy" value="www.example.com"/>
- <string-array name="first_party_endpoints" num="1">
- <item value="url1"/>
- </string-array>
- <string-array name="service_provider_endpoints" num="2">
- <item value="url55"/>
- <item value="url56"/>
- </string-array>
- </pbundle_as_map>
- </pbundle_as_map>
-</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
index 0d15efc..306e015 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
@@ -1,6 +1,6 @@
<app-info
apsCompliant="false"
- privacyPolicy="www.example.com">
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
<first-party-endpoints>
<item>url1</item>
</first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml
new file mode 100644
index 0000000..b026cf3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid-v1.xml
@@ -0,0 +1,24 @@
+<pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoints" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ <string name="website" value="www.example.com"/>
+</pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
index bce5179..7aae4a7 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
@@ -1,6 +1,5 @@
<pbundle_as_map name="app_info">
- <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
<string-array name="first_party_endpoints" num="1">
<item value="url1"/>
@@ -9,4 +8,7 @@
<item value="url55"/>
<item value="url56"/>
</string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml
new file mode 100644
index 0000000..810078e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/unrecognized-v1.xml
@@ -0,0 +1,25 @@
+<pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoints" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ <string name="website" value="www.example.com"/>
+ <string name="unrecognized" value="www.example.com"/>
+</pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
index 2512ca4..2c5cf86 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
@@ -2,7 +2,7 @@
<transparency-info>
<app-info
apsCompliant="false"
- privacyPolicy="www.example.com">
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
<first-party-endpoints>
<item>url1</item>
</first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
index d16caae..29c88d2 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
@@ -1,5 +1,16 @@
<transparency-info>
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com" developerId="dev1" applicationId="app1">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
<developer-info
name="max"
email="max@example.com"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
index c7bdd97..c46cec1 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
@@ -1,7 +1,6 @@
<pbundle_as_map name="transparency_info">
<pbundle_as_map name="app_info">
- <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
<string-array name="first_party_endpoints" num="1">
<item value="url1"/>
@@ -10,5 +9,8 @@
<item value="url55"/>
<item value="url56"/>
</string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
</pbundle_as_map>
</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
index d7a4e1a..b5e64b9 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
@@ -1,5 +1,18 @@
<pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
+ </pbundle_as_map>
<pbundle_as_map name="developer_info">
<string name="name" value="max"/>
<string name="email" value="max@example.com"/>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml
new file mode 100644
index 0000000..e8b0c17
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general-v1/od.xml
@@ -0,0 +1,70 @@
+<bundle>
+ <long name="version" value="1"/>
+ <pbundle_as_map name="safety_labels">
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="2">
+ <item value="1"/>
+ <item value="2"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="true"/>
+ <boolean name="ephemeral" value="true"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="security_labels">
+ <boolean name="is_data_deletable" value="true"/>
+ <boolean name="is_data_encrypted" value="false"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="third_party_verification">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ <pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ <string name="app_developer_registry_id" value="registry_id"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoints" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
index 5923079..f932982 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -1,20 +1,13 @@
-<app-metadata-bundles version="123">
+<app-metadata-bundles version="2">
<safety-labels>
<data-labels>
- <data-shared
- dataType="location_data_type_approx_location"
- isSharingOptional="false"
- purposes="app_functionality" />
- <data-shared
- dataType="location_data_type_precise_location"
- isSharingOptional="true"
- purposes="app_functionality|analytics" />
+ <data-shared dataType="location_data_type_approx_location" isSharingOptional="false" purposes="app_functionality"/>
+ <data-shared dataType="location_data_type_precise_location" isSharingOptional="true" purposes="app_functionality|analytics"/>
</data-labels>
</safety-labels>
- <system-app-safety-label declaration="true">
- </system-app-safety-label>
+ <system-app-safety-label declaration="true"/>
<transparency-info>
- <app-info apsCompliant="false" privacyPolicy="www.example.com">
+ <app-info applicationId="app1" apsCompliant="false" developerId="dev1" privacyPolicy="www.example.com">
<first-party-endpoints>
<item>url1</item>
</first-party-endpoints>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
index c24087e..c7def72 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -1,5 +1,5 @@
<bundle>
- <long name="version" value="123"/>
+ <long name="version" value="2"/>
<pbundle_as_map name="safety_labels">
<pbundle_as_map name="data_labels">
<pbundle_as_map name="data_shared">
@@ -26,7 +26,6 @@
</pbundle_as_map>
<pbundle_as_map name="transparency_info">
<pbundle_as_map name="app_info">
- <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
<string-array name="first_party_endpoints" num="1">
<item value="url1"/>
@@ -35,6 +34,9 @@
<item value="url55"/>
<item value="url56"/>
</string-array>
+ <boolean name="aps_compliant" value="false"/>
+ <string name="developer_id" value="dev1"/>
+ <string name="application_id" value="app1"/>
</pbundle_as_map>
</pbundle_as_map>
</bundle>
\ No newline at end of file
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index a5ac2be..18bda92 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -6,7 +6,7 @@
It's a python script that runs the framework linter,
and then (optionally) copies modified files back into the source tree.\
-Why python, you ask? Because python is cool ¯\_(ツ)_/¯.
+Why python, you ask? Because python is cool ¯\\\_(ツ)\_/¯.
Incidentally, this exposes a much simpler way to run individual lint checks
against individual modules, so it's useful beyond applying fixes.
@@ -15,7 +15,7 @@
Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
-directory. This script runs the lint, unpacks those files, and copies them back into the tree.
+directory. This script runs the lint, unpacks those files, and copies them back into the tree.
## How do I run it?
**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**