Merge "Update FR bug numbers of windowing_sdk flags" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ee7ace..e6e835b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
@@ -1123,3 +1124,16 @@
],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Wearable Sensing
+aconfig_declarations {
+ name: "android.app.wearable.flags-aconfig",
+ package: "android.app.wearable",
+ srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.wearable.flags-aconfig-java",
+ aconfig_declarations: "android.app.wearable.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/Android.bp b/api/Android.bp
index b3b18b6..ef64a89 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -115,6 +115,7 @@
"framework-pdf",
"framework-permission",
"framework-permission-s",
+ "framework-profiling",
"framework-scheduling",
"framework-sdkextensions",
"framework-statsd",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7ae3224..7ee4319 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -67,6 +67,7 @@
":framework-ondevicepersonalization-sources",
":framework-permission-sources",
":framework-permission-s-sources",
+ ":framework-profiling-sources",
":framework-scheduling-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
diff --git a/boot/Android.bp b/boot/Android.bp
index 228d060..cdfa7c80 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -122,6 +122,10 @@
module: "com.android.permission-bootclasspath-fragment",
},
{
+ apex: "com.android.profiling",
+ module: "com.android.profiling-bootclasspath-fragment",
+ },
+ {
apex: "com.android.scheduling",
module: "com.android.scheduling-bootclasspath-fragment",
},
diff --git a/core/api/current.txt b/core/api/current.txt
index e957676..b17e3343 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+ field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
@@ -53783,6 +53785,7 @@
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53792,6 +53795,7 @@
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
method public void removeViewImmediate(android.view.View);
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53811,6 +53815,8 @@
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 55ed1f5..d331455 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -187,6 +187,7 @@
method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
+ method @FlaggedApi("android.media.audio.sco_managed_by_audio") @NonNull public static android.media.BluetoothProfileConnectionInfo createHfpInfo();
method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
method public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6afc948..01f54ad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -414,6 +414,7 @@
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+ field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
}
public static final class Manifest.permission_group {
@@ -3157,6 +3158,7 @@
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
}
@@ -11303,6 +11305,12 @@
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ }
+
@Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -13441,6 +13449,7 @@
public abstract class Connection extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public final int getCallDirection();
method @IntRange(from=0) public final long getConnectTimeMillis();
method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6285eb3..084c71f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -147,6 +147,7 @@
* </p>
*/
@SystemService(Context.ACTIVITY_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class ActivityManager {
private static String TAG = "ActivityManager";
@@ -966,6 +967,7 @@
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
printCapabilitiesSummary(pw, caps);
final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String getCapabilitiesSummary(@ProcessCapability int caps) {
final StringBuilder sb = new StringBuilder();
printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@
* @return the value of the corresponding enums.proto ProcessStateEnum value.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final int processStateAmToProto(int amInt) {
switch (amInt) {
case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@
public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
/** @hide Should this process state be considered a background state? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateBackground(int procState) {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
/** @hide Should this process state be considered in the cache? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateCached(int procState) {
return procState >= PROCESS_STATE_CACHED_ACTIVITY;
}
/** @hide Is this a foreground service type? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
}
@@ -1161,10 +1170,25 @@
mContext = context;
}
+ private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood(int currentUser) {
+ sCurrentUser$ravenwood = currentUser;
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ sCurrentUser$ravenwood = UserHandle.USER_NULL;
+ }
+
/**
* Returns whether the launch was successful.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultSuccessful(int result) {
return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
}
@@ -1173,6 +1197,7 @@
* Returns whether the launch result was a fatal error.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultFatalError(int result) {
return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
}
@@ -1343,6 +1368,7 @@
public @interface RestrictionLevel{}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
switch (level) {
case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@
* Returns "true" if the user interface is currently being messed with
* by a monkey.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isUserAMonkey() {
try {
return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@
}
}
+ /** @hide */
+ public static boolean isUserAMonkey$ravenwood() {
+ // Ravenwood environment is never considered a "monkey"
+ return false;
+ }
+
/**
* Returns "true" if device is running in a test harness.
*
@@ -4973,6 +5006,7 @@
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.INTERACT_ACROSS_USERS_FULL"
})
+ @android.ravenwood.annotation.RavenwoodReplace
public static int getCurrentUser() {
try {
return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@
}
}
+ /** @hide */
+ public static int getCurrentUser$ravenwood() {
+ return sCurrentUser$ravenwood;
+ }
+
/**
* @param userid the user's id. Zero indicates the default user.
* @hide
@@ -5320,6 +5359,7 @@
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isSystemReady() {
if (!sSystemReady) {
if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@
return sSystemReady;
}
+ /** @hide */
+ public static boolean isSystemReady$ravenwood() {
+ // Ravenwood environment is always considered as booted and ready
+ return true;
+ }
+
/**
* @hide
*/
@@ -5661,11 +5707,13 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String procStateToString(int procState) {
final String procStateStr;
switch (procState) {
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d540748..e2e2f1d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@
boolean focused, boolean newSessionId);
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
- boolean isAssistDataAllowedOnCurrentActivity();
+ boolean isAssistDataAllowed();
boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
in String callingPackageName, String callingAttributionTag);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 8b8576a..b5e5074 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -721,28 +721,28 @@
/**
* Returns whether the device is currently locked for the user.
* <p>
- * This returns the device locked state for the {@link Context}'s user. If this user is the
- * current user, then the device is considered "locked" when the lock screen is showing (i.e.
- * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
- * swipe), and the user has a PIN, pattern, or password.
+ * This method returns the device locked state for the {@link Context}'s user. The device is
+ * considered to be locked for a user when the user's apps are currently inaccessible and some
+ * form of lock screen authentication is required to regain access to them. The lock screen
+ * authentication typically uses PIN, pattern, password, or biometric. Some devices may support
+ * additional methods, such as unlock using a paired smartwatch. "Swipe" does not count as
+ * authentication; if the lock screen is dismissible with swipe, for example due to the lock
+ * screen being set to Swipe or due to the device being kept unlocked by being near a trusted
+ * bluetooth device or in a trusted location, the device is considered unlocked.
+ * <div class="note">
* <p>
- * Note: the above definition implies that a user with no PIN, pattern, or password is never
- * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
- * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
- * authentication has just completed or a trust agent is keeping the device unlocked, since in
- * these cases the lock screen is dismissible with swipe.
+ * <b>Note:</b> In the case of multiple full users, each user can have their own lock screen
+ * authentication configured. The device-locked state may differ between different users. For
+ * example, the device may be unlocked for the current user, but locked for a non-current user
+ * if lock screen authentication would be required to access that user's apps after switching to
+ * that user.
* <p>
- * For a user that is not the current user but can be switched to (usually this means "another
- * full user"), and that has a PIN, pattern, or password, the device is always considered
- * locked.
- * <p>
- * For a profile with a unified challenge, the device locked state is the same as that of the
- * parent user.
- * <p>
- * For a profile with a separate challenge, the device becomes unlocked when the profile's PIN,
- * pattern, password, or biometric is verified. It becomes locked when the parent user becomes
- * locked, the screen turns off, the device reboots, the device policy controller locks the
- * profile, or the timeout set by the device policy controller expires.
+ * In the case of a profile, when the device goes to the main lock screen, up to two layers of
+ * authentication may be required to regain access to the profile's apps: one to unlock the main
+ * lock screen, and one to unlock the profile (when a separate profile challenge is required).
+ * For a profile, the device is considered to be locked as long as any challenge remains, either
+ * the parent user's challenge (when applicable) or the profile's challenge (when applicable).
+ * </div>
*
* @return {@code true} if the device is currently locked for the user
* @see #isKeyguardLocked()
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb..8883907 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5946,6 +5946,12 @@
// there is enough space to do so (and fall back to the left edge if not).
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "setting evenly divided mode on action list");
+ }
+ big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+ }
}
big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@
// Remove full-length color spans and ensure text contrast with the button fill.
title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
- button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+ final CharSequence label = ensureColorSpanContrast(title, p);
+ if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+ }
+ button.setCharSequence(R.id.action0, "glueLabel", label);
+ } else {
+ button.setTextViewText(R.id.action0, label);
+ }
int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
if (tombstone) {
@@ -6438,7 +6452,14 @@
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(buttonFillColor));
if (p.mCallStyleActions) {
- button.setImageViewIcon(R.id.action0, action.getIcon());
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+ }
+ button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+ } else {
+ button.setImageViewIcon(R.id.action0, action.getIcon());
+ }
boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
button.setBoolean(R.id.action0, "setIsPriority", priority);
int minWidthDimen =
@@ -9565,6 +9586,15 @@
* </pre>
*/
public static class CallStyle extends Style {
+ /**
+ * @hide
+ */
+ public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+ /**
+ * @hide
+ */
+ public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
/**
* @hide
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index edf0a46..6a114f9 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -114,6 +114,22 @@
}
/**
+ * Remove all Messages from the Handler with the given code.
+ *
+ * This method intentionally avoids creating the Handler if it doesn't
+ * already exist.
+ */
+ private static void handlerRemoveMessages(int what) {
+ synchronized (sLock) {
+ if (sHandler == null) {
+ // Nothing to remove
+ return;
+ }
+ getHandler().removeMessages(what);
+ }
+ }
+
+ /**
* Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
*
* Used by SharedPreferences$Editor#startCommit().
@@ -156,17 +172,13 @@
long startTime = System.currentTimeMillis();
boolean hadMessages = false;
- Handler handler = getHandler();
-
synchronized (sLock) {
- if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
- // Delayed work will be processed at processPendingWork() below
- handler.removeMessages(QueuedWorkHandler.MSG_RUN);
-
- if (DEBUG) {
- hadMessages = true;
- Log.d(LOG_TAG, "waiting");
- }
+ if (DEBUG) {
+ hadMessages = getHandler().hasMessages(QueuedWorkHandler.MSG_RUN);
+ }
+ handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
+ if (DEBUG && hadMessages) {
+ Log.d(LOG_TAG, "waiting");
}
// We should not delay any work as this might delay the finishers
@@ -257,7 +269,7 @@
sWork = new LinkedList<>();
// Remove all msg-s as all work will be processed now
- getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
+ handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a271328..a045eae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.Flags.enableNightModeCache;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -31,6 +33,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.IpcDataCache;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@
}
}
+ private Integer getNightModeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getNightMode();
+ }
+ return -1;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the night mode for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getNightModeFromServer();
+ }
+ };
+
+ private static final String NIGHT_MODE_API = "getNightMode";
+
+ /**
+ * Cache the night mode for a user.
+ */
+ private final IpcDataCache<Void, Integer> mNightModeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+ /**
+ * Invalidate the night mode cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ public static void invalidateNightModeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API);
+ }
+
/**
* Returns the currently configured night mode.
* <p>
@@ -890,14 +938,11 @@
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getNightMode();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableNightModeCache()) {
+ return mNightModeCache.query(null);
+ } else {
+ return getNightModeFromServer();
}
- return -1;
}
/**
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 0000000..1ae5264
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ namespace: "system_performance"
+ name: "enable_night_mode_cache"
+ description: "Enables the use of binder caching for system night mode."
+ bug: "255999432"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca086..eca0039 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -99,6 +100,13 @@
*/
public static final int STATUS_ACCESS_DENIED = 5;
+ /**
+ * The value of the status code that indicates the method called is not supported by the
+ * implementation of {@link WearableSensingService}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+ public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -106,7 +114,8 @@
STATUS_UNSUPPORTED,
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
- STATUS_ACCESS_DENIED
+ STATUS_ACCESS_DENIED,
+ STATUS_UNSUPPORTED_OPERATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 0000000..074ce9b
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.wearable"
+
+flag {
+ name: "enable_unsupported_operation_status_code"
+ namespace: "machine_learning"
+ description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+ bug: "301427767"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 629658b..19bce0b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -191,3 +191,11 @@
bug: "287487923"
is_fixed_read_only: true
}
+
+flag {
+ name: "force_multi_arch_native_libs_match"
+ namespace: "package_manager_service"
+ description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+ bug: "282783453"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607..d7e64b6 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,17 @@
bug: "316362775"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_permission_to_access_hidden_profiles"
+ namespace: "profile_experiences"
+ description: "Add permission to access API hidden users data via system APIs"
+ bug: "321988638"
+}
+
+flag {
+ name: "handle_interleaved_settings_for_private_space"
+ namespace: "profile_experiences"
+ description: "Handle listing of private space apps in settings pages with interleaved content"
+ bug: "323212460"
+}
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 0f66fcb..5cbf24f 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,16 +16,12 @@
package android.ddm;
-import static com.android.internal.util.Preconditions.checkArgument;
-
import android.util.Log;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
-import com.android.internal.annotations.VisibleForTesting;
-
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -35,10 +31,8 @@
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
/**
* Handle various requests related to profiling / debugging of the view system.
@@ -352,48 +346,17 @@
*
* The return value is encoded the same way as a single parameter (type + value)
*/
- private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+ private Chunk invokeViewMethod(View rootView, final View targetView, ByteBuffer in) {
int l = in.getInt();
String methodName = getString(in, l);
- Class<?>[] argTypes;
- Object[] args;
- if (!in.hasRemaining()) {
- argTypes = new Class<?>[0];
- args = new Object[0];
- } else {
- int nArgs = in.getInt();
- argTypes = new Class<?>[nArgs];
- args = new Object[nArgs];
-
- try {
- deserializeMethodParameters(args, argTypes, in);
- } catch (ViewMethodInvocationSerializationException e) {
- return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
- }
- }
-
- Method method;
try {
- method = targetView.getClass().getMethod(methodName, argTypes);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "No such method: " + e.getMessage());
- return createFailChunk(ERR_INVALID_PARAM,
- "No such method: " + e.getMessage());
- }
-
- try {
- Object result = ViewDebug.invokeViewMethod(targetView, method, args);
- Class<?> returnType = method.getReturnType();
- byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+ byte[] returnValue = ViewDebug.invokeViewMethod(targetView, methodName, in);
return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
+ } catch (ViewDebug.ViewMethodInvocationSerializationException e) {
+ return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
} catch (Exception e) {
- Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
- String msg = e.getCause().getMessage();
- if (msg == null) {
- msg = e.getCause().toString();
- }
- return createFailChunk(ERR_EXCEPTION, msg);
+ return createFailChunk(ERR_EXCEPTION, e.getMessage());
}
}
@@ -431,175 +394,4 @@
byte[] data = b.toByteArray();
return new Chunk(CHUNK_VUOP, data, 0, data.length);
}
-
- /**
- * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
- * buffer.
- *
- * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
- * be the same length, and will be set to the argument types of the data read.
- *
- * @hide
- */
- @VisibleForTesting
- public static void deserializeMethodParameters(
- Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
- ViewMethodInvocationSerializationException {
- checkArgument(args.length == argTypes.length);
-
- for (int i = 0; i < args.length; i++) {
- char typeSignature = in.getChar();
- boolean isArray = typeSignature == SIG_ARRAY;
- if (isArray) {
- char arrayType = in.getChar();
- if (arrayType != SIG_BYTE) {
- // This implementation only supports byte-arrays for now.
- throw new ViewMethodInvocationSerializationException(
- "Unsupported array parameter type (" + typeSignature
- + ") to invoke view method @argument " + i);
- }
-
- int arrayLength = in.getInt();
- if (arrayLength > in.remaining()) {
- // The sender did not actually sent the specified amount of bytes. This
- // avoids a malformed packet to trigger an out-of-memory error.
- throw new BufferUnderflowException();
- }
-
- byte[] byteArray = new byte[arrayLength];
- in.get(byteArray);
-
- argTypes[i] = byte[].class;
- args[i] = byteArray;
- } else {
- switch (typeSignature) {
- case SIG_BOOLEAN:
- argTypes[i] = boolean.class;
- args[i] = in.get() != 0;
- break;
- case SIG_BYTE:
- argTypes[i] = byte.class;
- args[i] = in.get();
- break;
- case SIG_CHAR:
- argTypes[i] = char.class;
- args[i] = in.getChar();
- break;
- case SIG_SHORT:
- argTypes[i] = short.class;
- args[i] = in.getShort();
- break;
- case SIG_INT:
- argTypes[i] = int.class;
- args[i] = in.getInt();
- break;
- case SIG_LONG:
- argTypes[i] = long.class;
- args[i] = in.getLong();
- break;
- case SIG_FLOAT:
- argTypes[i] = float.class;
- args[i] = in.getFloat();
- break;
- case SIG_DOUBLE:
- argTypes[i] = double.class;
- args[i] = in.getDouble();
- break;
- case SIG_STRING: {
- argTypes[i] = String.class;
- int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
- byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
- in.get(rawStringBuffer);
- args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
- break;
- }
- default:
- Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
- throw new ViewMethodInvocationSerializationException(
- "Unsupported parameter type (" + typeSignature
- + ") to invoke view method.");
- }
- }
-
- }
- }
-
- /**
- * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
- * @hide
- */
- @VisibleForTesting
- public static byte[] serializeReturnValue(Class<?> type, Object value)
- throws ViewMethodInvocationSerializationException, IOException {
- ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
- DataOutputStream dos = new DataOutputStream(byteOutStream);
-
- if (type.isArray()) {
- if (!type.equals(byte[].class)) {
- // Only byte arrays are supported currently.
- throw new ViewMethodInvocationSerializationException(
- "Unsupported array return type (" + type + ")");
- }
- byte[] byteArray = (byte[]) value;
- dos.writeChar(SIG_ARRAY);
- dos.writeChar(SIG_BYTE);
- dos.writeInt(byteArray.length);
- dos.write(byteArray);
- } else if (boolean.class.equals(type)) {
- dos.writeChar(SIG_BOOLEAN);
- dos.write((boolean) value ? 1 : 0);
- } else if (byte.class.equals(type)) {
- dos.writeChar(SIG_BYTE);
- dos.writeByte((byte) value);
- } else if (char.class.equals(type)) {
- dos.writeChar(SIG_CHAR);
- dos.writeChar((char) value);
- } else if (short.class.equals(type)) {
- dos.writeChar(SIG_SHORT);
- dos.writeShort((short) value);
- } else if (int.class.equals(type)) {
- dos.writeChar(SIG_INT);
- dos.writeInt((int) value);
- } else if (long.class.equals(type)) {
- dos.writeChar(SIG_LONG);
- dos.writeLong((long) value);
- } else if (double.class.equals(type)) {
- dos.writeChar(SIG_DOUBLE);
- dos.writeDouble((double) value);
- } else if (float.class.equals(type)) {
- dos.writeChar(SIG_FLOAT);
- dos.writeFloat((float) value);
- } else if (String.class.equals(type)) {
- dos.writeChar(SIG_STRING);
- dos.writeUTF(value != null ? (String) value : "");
- } else {
- dos.writeChar(SIG_VOID);
- }
-
- return byteOutStream.toByteArray();
- }
-
- // Prefixes for simple primitives. These match the JNI definitions.
- private static final char SIG_ARRAY = '[';
- private static final char SIG_BOOLEAN = 'Z';
- private static final char SIG_BYTE = 'B';
- private static final char SIG_SHORT = 'S';
- private static final char SIG_CHAR = 'C';
- private static final char SIG_INT = 'I';
- private static final char SIG_LONG = 'J';
- private static final char SIG_FLOAT = 'F';
- private static final char SIG_DOUBLE = 'D';
- private static final char SIG_VOID = 'V';
- // Prefixes for some commonly used objects
- private static final char SIG_STRING = 'R';
-
- /**
- * @hide
- */
- @VisibleForTesting
- public static class ViewMethodInvocationSerializationException extends Exception {
- ViewMethodInvocationSerializationException(String message) {
- super(message);
- }
- }
}
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index d51e62e..1488cff 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -15,6 +15,8 @@
*/
package android.hardware.biometrics;
+import android.hardware.biometrics.BiometricSourceType;
+
/**
* Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system
* services (e.g. SystemUI) to register a listener for updates about the current state of biometric
@@ -49,4 +51,15 @@
* @param userId The user Id for the requested authentication
*/
void onAuthenticationFailed(int requestReason, int userId);
+
+ /**
+ * Defines behavior in response to biometric being acquired.
+ * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+ * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+ * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+ * a known acquired message.
+ */
+ void onAuthenticationAcquired(
+ in BiometricSourceType biometricSourceType, int requestReason, int acquiredInfo
+ );
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 665d8d2..47f5b4c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5313,7 +5313,7 @@
* </code></pre>
* <ul>
* <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
- * <li>AE_TARGET_FPS_RANGE: {{<em>, 30}, {</em>, 60}}</li>
+ * <li>AE_TARGET_FPS_RANGE: {{<em>, 30}, {</em>, 60}}</li>
* <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
* </ul>
* <p>This key is available on all devices.</p>
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5078dc35..46705a3 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -29,6 +29,7 @@
/**
* Encapsulates a collection of attributes describing information about a vibration.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class VibrationAttributes implements Parcelable {
private static final String TAG = "VibrationAttributes";
@@ -463,6 +464,7 @@
* Builder class for {@link VibrationAttributes} objects.
* By default, all information is set to UNKNOWN.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Builder {
private int mUsage = USAGE_UNKNOWN;
private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde699..bef6456 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -247,6 +248,44 @@
}
/**
+ * Updates a contact key entry's local verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param localVerificationState the new local verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int localVerificationState) {
+ validateVerificationState(localVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Updates a contact key entry's remote verification state that belongs to the caller app.
*
* @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
}
+ /**
+ * Updates a contact key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+
private static void validateVerificationState(int verificationState) {
if (verificationState != UNVERIFIED
&& verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@
public boolean removeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
- Bundle extras = new Bundle();
+ final Bundle extras = new Bundle();
extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- Bundle response = nullSafeCall(mContentResolver,
+ final Bundle response = nullSafeCall(mContentResolver,
ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@
}
/**
+ * Updates a self key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ Bundle extras = new Bundle();
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Maximum size of a contact key.
*/
public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050..99863d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 59ec605..9db1060 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -162,6 +162,12 @@
public float alpha;
/**
+ * Sets a property on this window indicating that its visible region should be considered when
+ * computing TrustedPresentation Thresholds.
+ */
+ public boolean canOccludePresentation;
+
+ /**
* The input token for the window to which focus should be transferred when this input window
* can be successfully focused. If null, this input window will not transfer its focus to
* any other window.
@@ -205,6 +211,7 @@
focusTransferTarget = other.focusTransferTarget;
contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
alpha = other.alpha;
+ canOccludePresentation = other.canOccludePresentation;
}
@Override
@@ -219,6 +226,7 @@
.append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
.append(", contentSize=").append(contentSize)
.append(", alpha=").append(alpha)
+ .append(", canOccludePresentation=").append(canOccludePresentation)
.toString();
}
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 0000000..ee55737
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+ private static ScreenRecordingCallbacks sInstance;
+ private static final Object sLock = new Object();
+
+ private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+ new ArrayMap<>();
+
+ private IScreenRecordingCallback mCallbackNotifier;
+ private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+ private ScreenRecordingCallbacks() {}
+
+ private static @NonNull IWindowManager getWindowManagerService() {
+ return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+ }
+
+ static ScreenRecordingCallbacks getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ScreenRecordingCallbacks();
+ }
+ return sInstance;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ @ScreenRecordingState
+ int addCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ if (mCallbackNotifier == null) {
+ mCallbackNotifier =
+ new IScreenRecordingCallback.Stub() {
+ @Override
+ public void onScreenRecordingStateChanged(
+ boolean visibleInScreenRecording) {
+ int state =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ notifyCallbacks(state);
+ }
+ };
+ try {
+ boolean visibleInScreenRecording =
+ getWindowManagerService()
+ .registerScreenRecordingCallback(mCallbackNotifier);
+ mState =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ mCallbacks.put(callback, executor);
+ return mState;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ mCallbacks.remove(callback);
+ if (mCallbacks.isEmpty()) {
+ try {
+ getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mCallbackNotifier = null;
+ }
+ }
+ }
+
+ private void notifyCallbacks(@ScreenRecordingState int state) {
+ List<Runnable> callbacks;
+ synchronized (sLock) {
+ mState = state;
+ if (mCallbacks.isEmpty()) {
+ return;
+ }
+
+ callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ Consumer<Integer> callback = mCallbacks.keyAt(i);
+ Executor executor = mCallbacks.valueAt(i);
+ callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).run();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3ed0385..3c0ac06 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -168,6 +168,8 @@
boolean isTrustedOverlay);
private static native void nativeSetDropInputMode(
long transactionObj, long nativeObject, int flags);
+ private static native void nativeSetCanOccludePresentation(long transactionObj,
+ long nativeObject, boolean canOccludePresentation);
private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -589,6 +591,28 @@
public static final int DISPLAY_DECORATION = 0x00000200;
/**
+ * Ignore any destination frame set on the layer. This is used when the buffer scaling mode
+ * is freeze and the destination frame is applied asynchronously with the buffer submission.
+ * This is needed to maintain compatibility for SurfaceView scaling behavior.
+ * See SurfaceView scaling behavior for more details.
+ * @hide
+ */
+ public static final int IGNORE_DESTINATION_FRAME = 0x00000400;
+
+ /**
+ * Special casing for layer that is a refresh rate indicator
+ * @hide
+ */
+ public static final int LAYER_IS_REFRESH_RATE_INDICATOR = 0x00000800;
+
+ /**
+ * Sets a property on this layer indicating that its visible region should be considered when
+ * computing TrustedPresentation Thresholds
+ * @hide
+ */
+ public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
+
+ /**
* Surface creation flag: Creates a surface where color components are interpreted
* as "non pre-multiplied" by their alpha channel. Of course this flag is
* meaningless for surfaces without an alpha channel. By default
@@ -4163,6 +4187,29 @@
}
/**
+ * Sets a property on this SurfaceControl and all its children indicating that the visible
+ * region of this SurfaceControl should be considered when computing TrustedPresentation
+ * Thresholds.
+ * <p>
+ * API Guidance:
+ * The goal of this API is to identify windows that can be used to occlude content on
+ * another window. This includes windows controlled by the user or the system. If the window
+ * is transient, like Toast or notification shade, the window should not set this flag since
+ * the user or the app cannot use the window to occlude content in a persistent manner. All
+ * apps should have this flag set.
+ * <p>
+ * The caller must hold the ACCESS_SURFACE_FLINGER permission.
+ * @hide
+ */
+ public Transaction setCanOccludePresentation(SurfaceControl sc,
+ boolean canOccludePresentation) {
+ checkPreconditions(sc);
+ final int value = (canOccludePresentation) ? CAN_OCCLUDE_PRESENTATION : 0;
+ nativeSetFlags(mNativeObject, sc.mNativeObject, value, CAN_OCCLUDE_PRESENTATION);
+ return this;
+ }
+
+ /**
* Sends a flush jank data transaction for the given surface.
* @hide
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0d2c2cc..2366ff7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+ private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
+ private int mInfrequentUpdateCount = 0;
+ private long mLastUpdateTimeMillis = 0;
+ private long mMinusOneFrameIntervalMillis = 0;
+ private long mMinusTwoFrameIntervalMillis = 0;
+ private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -20253,7 +20263,10 @@
}
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20371,10 @@
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
mParent.onDescendantInvalidated(this, this);
}
}
@@ -33131,11 +33147,20 @@
}
private int calculateFrameRateCategory(float sizePercentage) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
+ if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+ < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ } else {
+ return FRAME_RATE_CATEGORY_HIGH;
+ }
+ }
+
+ if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
return FRAME_RATE_CATEGORY_NORMAL;
}
+
+ return mLastFrameRateCategory;
}
private void votePreferredFrameRate() {
@@ -33144,22 +33169,22 @@
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ mLastFrameRateCategory = frameRateCateogry;
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
@@ -33238,4 +33263,27 @@
}
return 0;
}
+
+ /**
+ * This function is mainly used for migrating infrequent layer lagic
+ * from SurfaceFlinger to Toolkit.
+ * The infrequent layter logic includes:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ private void updateInfrequentCount() {
+ long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+ long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+ mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+ mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+ mLastUpdateTimeMillis = currentTimeMillis;
+ if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+ ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+ } else {
+ mInfrequentUpdateCount = 0;
+ }
+ }
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 25e0eca..4f1fb40 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
+ * Unless required by applicable law or agreed to in writing, softwareViewDebug
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -34,10 +36,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+import com.android.internal.annotations.VisibleForTesting;
+
import libcore.util.HexEncoding;
import java.io.BufferedOutputStream;
@@ -54,9 +59,11 @@
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
@@ -67,7 +74,6 @@
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -76,6 +82,9 @@
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
*/
public class ViewDebug {
+
+ private static final String TAG = "ViewDebug";
+
/**
* @deprecated This flag is now unused
*/
@@ -425,6 +434,7 @@
private static final String REMOTE_PROFILE = "PROFILE";
private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+ private static final String REMOTE_COMMAND_INVOKE_METHOD = "INVOKE_METHOD";
private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
@@ -555,6 +565,8 @@
requestLayout(view, params[0]);
} else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
profile(view, clientStream, params[0]);
+ } else if (REMOTE_COMMAND_INVOKE_METHOD.equals(command)) {
+ invokeViewMethod(view, clientStream, params);
}
}
}
@@ -1825,46 +1837,84 @@
Log.d(tag, sb.toString());
}
+ private static void invokeViewMethod(View root, OutputStream clientStream, String[] params)
+ throws IOException {
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+ try {
+ if (params.length < 2) {
+ throw new IllegalArgumentException("Missing parameter");
+ }
+ View targetView = findView(root, params[0]);
+ if (targetView == null) {
+ throw new IllegalArgumentException("View not found: " + params[0]);
+ }
+ String method = params[1];
+ ByteBuffer args = ByteBuffer.wrap(params.length < 2
+ ? new byte[0]
+ : Base64.decode(params[2], Base64.NO_WRAP));
+ byte[] result = invokeViewMethod(targetView, method, args);
+ out.write("1");
+ out.newLine();
+ out.write(Base64.encodeToString(result, Base64.NO_WRAP));
+ out.newLine();
+ } catch (Exception e) {
+ out.write("-1");
+ out.newLine();
+ out.write(e.getMessage());
+ out.newLine();
+ } finally {
+ out.close();
+ }
+ }
+
/**
* Invoke a particular method on given view.
* The given method is always invoked on the UI thread. The caller thread will stall until the
* method invocation is complete. Returns an object equal to the result of the method
* invocation, null if the method is declared to return void
+ * @param params all the method parameters encoded in a byteArray
* @throws Exception if the method invocation caused any exception
* @hide
*/
- public static Object invokeViewMethod(final View view, final Method method,
- final Object[] args) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AtomicReference<Object> result = new AtomicReference<Object>();
- final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+ public static byte[] invokeViewMethod(View targetView, String methodName, ByteBuffer params)
+ throws ViewMethodInvocationSerializationException {
+ Class<?>[] argTypes;
+ Object[] args;
+ if (!params.hasRemaining()) {
+ argTypes = new Class<?>[0];
+ args = new Object[0];
+ } else {
+ int nArgs = params.getInt();
+ argTypes = new Class<?>[nArgs];
+ args = new Object[nArgs];
- view.post(new Runnable() {
- @Override
- public void run() {
- try {
- result.set(method.invoke(view, args));
- } catch (InvocationTargetException e) {
- exception.set(e.getCause());
- } catch (Exception e) {
- exception.set(e);
- }
+ deserializeMethodParameters(args, argTypes, params);
+ }
- latch.countDown();
- }
- });
+ Method method;
+ try {
+ method = targetView.getClass().getMethod(methodName, argTypes);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "No such method: " + e.getMessage());
+ throw new ViewMethodInvocationSerializationException(
+ "No such method: " + e.getMessage());
+ }
try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ // Invoke the method on Views handler
+ FutureTask<Object> task = new FutureTask<>(() -> method.invoke(targetView, args));
+ targetView.post(task);
+ Object result = task.get();
+ Class<?> returnType = method.getReturnType();
+ return serializeReturnValue(returnType, returnType.cast(result));
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+ String msg = e.getCause().getMessage();
+ if (msg == null) {
+ msg = e.getCause().toString();
+ }
+ throw new RuntimeException(msg);
}
-
- if (exception.get() != null) {
- throw new RuntimeException(exception.get());
- }
-
- return result.get();
}
/**
@@ -1961,4 +2011,175 @@
*/
Bitmap createBitmap();
}
+
+ /**
+ * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+ * buffer.
+ *
+ * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+ * be the same length, and will be set to the argument types of the data read.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static void deserializeMethodParameters(
+ Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+ ViewMethodInvocationSerializationException {
+ checkArgument(args.length == argTypes.length);
+
+ for (int i = 0; i < args.length; i++) {
+ char typeSignature = in.getChar();
+ boolean isArray = typeSignature == SIG_ARRAY;
+ if (isArray) {
+ char arrayType = in.getChar();
+ if (arrayType != SIG_BYTE) {
+ // This implementation only supports byte-arrays for now.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array parameter type (" + typeSignature
+ + ") to invoke view method @argument " + i);
+ }
+
+ int arrayLength = in.getInt();
+ if (arrayLength > in.remaining()) {
+ // The sender did not actually sent the specified amount of bytes. This
+ // avoids a malformed packet to trigger an out-of-memory error.
+ throw new BufferUnderflowException();
+ }
+
+ byte[] byteArray = new byte[arrayLength];
+ in.get(byteArray);
+
+ argTypes[i] = byte[].class;
+ args[i] = byteArray;
+ } else {
+ switch (typeSignature) {
+ case SIG_BOOLEAN:
+ argTypes[i] = boolean.class;
+ args[i] = in.get() != 0;
+ break;
+ case SIG_BYTE:
+ argTypes[i] = byte.class;
+ args[i] = in.get();
+ break;
+ case SIG_CHAR:
+ argTypes[i] = char.class;
+ args[i] = in.getChar();
+ break;
+ case SIG_SHORT:
+ argTypes[i] = short.class;
+ args[i] = in.getShort();
+ break;
+ case SIG_INT:
+ argTypes[i] = int.class;
+ args[i] = in.getInt();
+ break;
+ case SIG_LONG:
+ argTypes[i] = long.class;
+ args[i] = in.getLong();
+ break;
+ case SIG_FLOAT:
+ argTypes[i] = float.class;
+ args[i] = in.getFloat();
+ break;
+ case SIG_DOUBLE:
+ argTypes[i] = double.class;
+ args[i] = in.getDouble();
+ break;
+ case SIG_STRING: {
+ argTypes[i] = String.class;
+ int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+ byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+ in.get(rawStringBuffer);
+ args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+ break;
+ }
+ default:
+ Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported parameter type (" + typeSignature
+ + ") to invoke view method.");
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+ * @hide
+ */
+ @VisibleForTesting
+ public static byte[] serializeReturnValue(Class<?> type, Object value)
+ throws ViewMethodInvocationSerializationException, IOException {
+ ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+ DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+ if (type.isArray()) {
+ if (!type.equals(byte[].class)) {
+ // Only byte arrays are supported currently.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array return type (" + type + ")");
+ }
+ byte[] byteArray = (byte[]) value;
+ dos.writeChar(SIG_ARRAY);
+ dos.writeChar(SIG_BYTE);
+ dos.writeInt(byteArray.length);
+ dos.write(byteArray);
+ } else if (boolean.class.equals(type)) {
+ dos.writeChar(SIG_BOOLEAN);
+ dos.write((boolean) value ? 1 : 0);
+ } else if (byte.class.equals(type)) {
+ dos.writeChar(SIG_BYTE);
+ dos.writeByte((byte) value);
+ } else if (char.class.equals(type)) {
+ dos.writeChar(SIG_CHAR);
+ dos.writeChar((char) value);
+ } else if (short.class.equals(type)) {
+ dos.writeChar(SIG_SHORT);
+ dos.writeShort((short) value);
+ } else if (int.class.equals(type)) {
+ dos.writeChar(SIG_INT);
+ dos.writeInt((int) value);
+ } else if (long.class.equals(type)) {
+ dos.writeChar(SIG_LONG);
+ dos.writeLong((long) value);
+ } else if (double.class.equals(type)) {
+ dos.writeChar(SIG_DOUBLE);
+ dos.writeDouble((double) value);
+ } else if (float.class.equals(type)) {
+ dos.writeChar(SIG_FLOAT);
+ dos.writeFloat((float) value);
+ } else if (String.class.equals(type)) {
+ dos.writeChar(SIG_STRING);
+ dos.writeUTF(value != null ? (String) value : "");
+ } else {
+ dos.writeChar(SIG_VOID);
+ }
+
+ return byteOutStream.toByteArray();
+ }
+
+ // Prefixes for simple primitives. These match the JNI definitions.
+ private static final char SIG_ARRAY = '[';
+ private static final char SIG_BOOLEAN = 'Z';
+ private static final char SIG_BYTE = 'B';
+ private static final char SIG_SHORT = 'S';
+ private static final char SIG_CHAR = 'C';
+ private static final char SIG_INT = 'I';
+ private static final char SIG_LONG = 'J';
+ private static final char SIG_FLOAT = 'F';
+ private static final char SIG_DOUBLE = 'D';
+ private static final char SIG_VOID = 'V';
+ // Prefixes for some commonly used objects
+ private static final char SIG_STRING = 'R';
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class ViewMethodInvocationSerializationException extends Exception {
+ ViewMethodInvocationSerializationException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ae9c109..c27b2b1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1025,6 +1025,13 @@
// time for revaluating the idle status before lowering the frame rate.
private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+ /*
+ * the variables below are used to determine whther a dVRR feature should be enabled
+ */
+
+ // Used to determine whether to suppress boost on typing
+ private boolean mShouldSuppressBoostOnTyping = false;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -12285,7 +12292,7 @@
boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
|| motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP;
- boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+ boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
// use toolkitSetFrameRate flag to gate the change
return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
&& getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c788261..38cf490 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@
import com.android.window.flags.Flags;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -6117,4 +6119,65 @@
throw new UnsupportedOperationException(
"getDefaultToken is not implemented");
}
+
+ /** @hide */
+ @Target(ElementType.TYPE_USE)
+ @IntDef(
+ prefix = {"SCREEN_RECORDING_STATE"},
+ value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ScreenRecordingState {}
+
+ /** Indicates the app that registered the callback is not visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+ /** Indicates the app that registered the callback is visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+ /**
+ * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+ * visible in screen recording or was visible in screen recording and becomes invisible in
+ * screen recording.
+ *
+ * <p>An app is considered visible in screen recording if any activities owned by the
+ * registering process's UID are being recorded.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * windowManager.addScreenRecordingCallback(state -> {
+ * // handle change in screen recording state
+ * });
+ * </pre>
+ *
+ * @param executor The executor on which callback method will be invoked.
+ * @param callback The callback that will be invoked when screen recording visibility changes.
+ * @return the current screen recording state.
+ * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+ * @see #SCREEN_RECORDING_STATE_VISIBLE
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a screen recording callback.
+ *
+ * @param callback The callback to remove.
+ * @see #addScreenRecordingCallback(Executor, Consumer)
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad7..eaf45c4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.window.WindowProviderService.isWindowProviderService;
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -551,4 +553,25 @@
public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
+
+ @Override
+ public @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+ return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+ }
+ return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ }
+
+ @Override
+ public void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e..9d613bc 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@
description: "Feature flag for toolkit metrics collecting for frame rate decision"
bug: "301343249"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_default_normal_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category as NORMAL for default"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_by_size_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category based on size"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_velocity_mapping_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate based on velocity"
+ bug: "239979904"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d2..2f765ae 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.Compatibility;
@@ -47,8 +46,7 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
- public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+ static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
private static final String LOGTAG = "webkit";
private static final boolean TRACE = false;
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index e20357fa..1dc90ed 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -15,3 +15,5 @@
per-file Remote* = file:../appwidget/OWNERS
per-file Toast.java = juliacr@google.com, jeffdq@google.com
+
+per-file flags/notification_widget_flags.aconfig = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49..5cda3f2 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.RemotableViewMethod;
import android.widget.Button;
import android.widget.RemoteViews;
@@ -43,6 +57,14 @@
private final GradientDrawable mBackground;
private boolean mPriority;
+ private int mInitialDrawablePadding;
+ private int mIconSize;
+
+ private Drawable mIconToGlue;
+ private CharSequence mLabelToGlue;
+ private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+ private boolean mGluePending;
+
public EmphasizedNotificationButton(Context context) {
this(context, null);
}
@@ -58,10 +80,25 @@
public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
mRipple = (RippleDrawable) getBackground();
mRipple.mutate();
DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
mBackground = (GradientDrawable) inset.getDrawable();
+
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_icon_drawable_size);
+
+ try (TypedArray typedArray = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+ mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+ android.R.styleable.TextView_drawablePadding, 0);
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+ }
}
@RemotableViewMethod
@@ -95,19 +132,248 @@
return () -> setImageDrawable(drawable);
}
- private void setImageDrawable(Drawable drawable) {
+ private void setImageDrawable(@Nullable Drawable drawable) {
if (drawable != null) {
- drawable.mutate();
- drawable.setTintList(getTextColors());
- drawable.setTintBlendMode(BlendMode.SRC_IN);
- int iconSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_actions_icon_drawable_size);
- drawable.setBounds(0, 0, iconSize, iconSize);
+ prepareIcon(drawable);
}
setCompoundDrawablesRelative(drawable, null, null, null);
}
/**
+ * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+ * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+ * the latter to work.
+ *
+ * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+ * button is displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueIconAsync")
+ public void glueIcon(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ setIconToGlue(drawable);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueIconAsync(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ return () -> setIconToGlue(drawable);
+ }
+
+ private void setIconToGlue(@Nullable Drawable icon) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+ return;
+ }
+
+ prepareIcon(icon);
+
+ mIconToGlue = icon;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void prepareIcon(@NonNull Drawable drawable) {
+ drawable.mutate();
+ drawable.setTintList(getTextColors());
+ drawable.setTintBlendMode(BlendMode.SRC_IN);
+ drawable.setBounds(0, 0, mIconSize, mIconSize);
+ }
+
+ /**
+ * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+ * displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+ public void glueLabel(@Nullable CharSequence label) {
+ setLabelToGlue(label);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueLabelAsync(@Nullable CharSequence label) {
+ return () -> setLabelToGlue(label);
+ }
+
+ private void setLabelToGlue(@Nullable CharSequence label) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+ return;
+ }
+
+ mLabelToGlue = label;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+ + "gluedLayoutDirection = " + mGluedLayoutDirection);
+ }
+
+ if (layoutDirection != mGluedLayoutDirection) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+ }
+ mGluePending = true;
+ }
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void glueIconAndLabelIfNeeded() {
+ // Don't need to glue:
+
+ if (!mGluePending) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+ }
+ return;
+ }
+
+ if (mIconToGlue == null && mLabelToGlue == null) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+ }
+ mGluePending = false;
+ return;
+ }
+
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+ return;
+ }
+
+ // Not ready to glue yet:
+
+ if (!isLayoutDirectionResolved()) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: "
+ + "layout direction not resolved; doing nothing");
+ }
+ return;
+ }
+
+ // Ready to glue but don't have an icon *and* a label:
+ //
+ // (Note that this will *not* happen while the button is being initialized, since we won't
+ // be ready to glue. This can only happen if the button is initialized and displayed and
+ // *then* someone calls glueIcon or glueLabel.
+
+ if (mIconToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+ return;
+ }
+
+ if (mLabelToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+ return;
+ }
+
+ // Can't glue:
+
+ final int layoutDirection = getLayoutDirection();
+ if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: "
+ + "resolved layout direction neither LTR nor RTL; "
+ + "doing nothing");
+ return;
+ }
+
+ // No excuses left, let's glue it!
+
+ glueIconAndLabel(layoutDirection);
+
+ mGluePending = false;
+ mGluedLayoutDirection = layoutDirection;
+ }
+
+ // Unicode replacement character
+ private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+ // Unicode no-break space
+ private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+ private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+ private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+ private static final String FIRST_STRONG_ISOLATE = "\u2068";
+ private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+ private void glueIconAndLabel(int layoutDirection) {
+ final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "glueIconAndLabel: "
+ + "icon = " + mIconToGlue + ", "
+ + "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+ + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+ + "rtlLayout = " + rtlLayout);
+ }
+
+ logIfTextDirectionNotFirstStrong();
+
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+ // The text direction of the label might not match the layout direction of the button, so
+ // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+ // layout direction. This puts the icon, padding, and label in the right order.
+ builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+ appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+ appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+ // If the text and layout directions are different, we would end up with the *label* in the
+ // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+ // automatic text direction heuristic that Android uses by default.
+ builder.append(FIRST_STRONG_ISOLATE);
+
+ appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+
+ setText(builder);
+ }
+
+ private void logIfTextDirectionNotFirstStrong() {
+ if (!isTextDirectionResolved()) {
+ Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+ + "letting View assume FIRST STRONG");
+ }
+ final int textDirection = getTextDirection();
+ if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+ Log.w(TAG, "glueIconAndLabel: "
+ + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+ + "but found " + textDirection + "; "
+ + "will use a FIRST STRONG ISOLATE regardless");
+ }
+ }
+
+ private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+ final int spanStart = builder.length();
+ builder.append(text);
+ final int spanEnd = builder.length();
+ builder.setSpan(span, spanStart, spanEnd, 0);
+ }
+
+ /**
* Sets whether this view is a priority over its peers (which affects width).
* Specifically, this is used by {@link NotificationActionListLayout} to give this view width
* priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@
public boolean isPriority() {
return mPriority;
}
+
+ private static class SpacerSpan extends ReplacementSpan {
+ private int mWidth;
+
+ SpacerSpan(int width) {
+ mWidth = width;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "width = " + mWidth + "px");
+ }
+ }
+
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+ @Nullable Paint.FontMetricsInt fontMetrics) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "getSize returning " + mWidth + "px");
+ }
+
+ return mWidth;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+ float x, int top, int y, int bottom, @NonNull Paint paint) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "drawing nothing");
+ }
+
+ // Draw nothing, it's a spacer.
+ }
+
+ private static final String TAG = "SpacerSpan";
+ }
+
+ private static class CenterBesideImageSpan extends MetricAffectingSpan {
+ private int mImageHeight;
+
+ private boolean mMeasured;
+ private int mBaselineShiftOffset;
+
+ CenterBesideImageSpan(int imageHeight) {
+ mImageHeight = imageHeight;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+ }
+ }
+
+ @Override
+ public void updateMeasureState(@NonNull TextPaint textPaint) {
+ final int textHeight = (int) -textPaint.ascent();
+
+ /*
+ * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+ * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+ */
+ if (textHeight < mImageHeight) {
+ mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+ } else {
+ mBaselineShiftOffset = 0;
+ }
+
+ mMeasured = true;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "updateMeasureState: "
+ + "imageHeight = " + mImageHeight + "px, "
+ + "textHeight = " + textHeight + "px, "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint textPaint) {
+ if (textPaint == null) {
+ Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+ return;
+ }
+
+ if (!mMeasured) {
+ Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "updateDrawState: "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ private static final String TAG = "CenterBesideImageSpan";
+ }
+
+ private static final String TAG = "EmphasizedNotificationButton";
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9..69d2544 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
import android.annotation.DimenRes;
import android.app.Notification;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -41,13 +45,13 @@
*/
@RemoteViews.RemoteView
public class NotificationActionListLayout extends LinearLayout {
-
private final int mGravity;
private int mTotalWidth = 0;
private int mExtraStartPadding = 0;
private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
private boolean mEmphasizedMode;
+ private boolean mEvenlyDividedMode;
private int mDefaultPaddingBottom;
private int mDefaultPaddingTop;
private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@
}
}
+ private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+ final int numChildren = getChildCount();
+ int childMarginSum = 0;
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ childMarginSum += lp.leftMargin + lp.rightMargin;
+ }
+ }
+
+ final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+ final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "measuring evenly divided width: "
+ + "numChildren = " + numChildren + ", "
+ + "innerWidth = " + innerWidth + "px, "
+ + "childMarginSum = " + childMarginSum + "px, "
+ + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+ + "childWidth = " + childWidth + "px, "
+ + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+ }
+
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.measure(childWidthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ return innerWidth;
+ }
+
private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
boolean collapsePriorityActions) {
final int numChildren = getChildCount();
@@ -208,11 +248,16 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
countAndRebuildMeasureOrder();
final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
- int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- false /* collapsePriorityButtons */);
- if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ int usedWidth;
+ if (mEvenlyDividedMode) {
+ usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+ } else {
usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- true /* collapsePriorityButtons */);
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
}
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@
}
/**
+ * Sets whether the available width should be distributed evenly among the action buttons.
+ *
+ * When enabled, the available width (after subtracting this layout's padding and all of the
+ * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+ * to that exact width, even if it is less <em>or more</em> width than they need.
+ *
+ * When disabled, the available width is allocated as buttons need; if that exceeds the
+ * available width, priority buttons are collapsed to just their icon to save space.
+ *
+ * @param evenlyDividedMode whether to enable evenly divided mode
+ */
+ @RemotableViewMethod
+ public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+ if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+ + "leaving evenly divided mode disabled");
+ return;
+ }
+
+ if (evenlyDividedMode == mEvenlyDividedMode) {
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+ + "requesting layout");
+ }
+ mEvenlyDividedMode = evenlyDividedMode;
+ requestLayout();
+ }
+
+ /**
* Set whether the list is in a mode where some actions are emphasized. This will trigger an
* equal measuring where all actions are full height and change a few parameters like
* the padding.
@@ -410,4 +487,5 @@
}
}
+ private static final String TAG = "NotificationActionListLayout";
}
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ae23942..bed7768 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -75,6 +75,7 @@
jfieldID windowToken;
jfieldID focusTransferTarget;
jfieldID alpha;
+ jfieldID canOccludePresentation;
} gInputWindowHandleClassInfo;
static struct {
@@ -327,6 +328,8 @@
javaObjectForIBinder(env, windowInfo.windowToken));
env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.alpha, windowInfo.alpha);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.canOccludePresentation,
+ windowInfo.canOccludePresentation);
return inputWindowHandle;
}
@@ -451,6 +454,9 @@
GET_FIELD_ID(gInputWindowHandleClassInfo.alpha, clazz, "alpha", "F");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.canOccludePresentation, clazz,
+ "canOccludePresentation", "Z");
+
jclass surfaceControlClazz;
FIND_CLASS(surfaceControlClazz, "android/view/SurfaceControl");
GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject,
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 2f29cae..917d283 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/jni/android_opengl_EGL15.cpp b/core/jni/android_opengl_EGL15.cpp
index b9c36b9..447b8ec 100644
--- a/core/jni/android_opengl_EGL15.cpp
+++ b/core/jni/android_opengl_EGL15.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index cdc9852..ffd75ea 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
@@ -54,11 +55,11 @@
jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
- eglsyncClass = (jclass)_env->NewGlobalRef(eglsyncClassLocal);
+ eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
- eglsyncGetHandleID = _env->GetMethodID(eglsyncClassLocal, "getNativeHandle", "()J");
+ eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
}
static void *
@@ -72,6 +73,14 @@
return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
}
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+ EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+ EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
+
+ return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
+}
+
// --------------------------------------------------------------------------
/* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
static jboolean
@@ -89,21 +98,12 @@
return (jboolean)_returnValue;
}
-static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
- EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
- EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
-
- return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
-}
-
static const char *classPathName = "android/opengl/EGLExt";
static const JNINativeMethod methods[] = {
- {"_nativeClassInit", "()V", (void *)nativeClassInit},
- {"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z",
- (void *)android_eglPresentationTimeANDROID},
- {"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I",
- (void *)android_eglDupNativeFenceFDANDROID},
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
+{"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I", (void *)android_eglDupNativeFenceFDANDROID },
};
int register_android_opengl_jni_EGLExt(JNIEnv *_env)
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index d65b498..2d921ad 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 3638b87..35a9a68 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 9724e6c..e04b56e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 1ffa4ec..bccbda6 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index d832558..165262e 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES2/gl2.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 719c6b3..d3fe439 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl3.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index afe7c63..b123f9d 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 8127433..1e4049b 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl31.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index 7ed7548..e0175f0 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 21de723..ef29c88 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 58cee98..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
+ <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+ <p>This permission is only granted to system apps.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ @FlaggedApi("android.provider.user_keys")
+ -->
+ <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+ android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to set default account for new contacts.
<p> This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
android:description="@string/permdesc_detectScreenCapture"
android:protectionLevel="normal" />
+ <!-- Allows an application to get notified when it is being recorded.
+ <p>Protection level: normal
+ @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+ android:protectionLevel="normal" />
+
<!-- ======================================== -->
<!-- Permissions for factory reset protection -->
<!-- ======================================== -->
@@ -3188,6 +3208,14 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dd6e79e..be96cc2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
<!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
<string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+ <!-- Title of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+ <!-- Summary of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecuritySummary">Review settings</string>
+ <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+ <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+ <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+ <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+ <!-- Label for the button that links to the cellular network security settings. -->
+ <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+ <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+ <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
<!-- android.net.http Error strings --> <skip />
<!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
<string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
<string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 699c8ac..603b902 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
<java-symbol type="string" name="roamingText8" />
<java-symbol type="string" name="roamingText9" />
<java-symbol type="string" name="roamingTextSearching" />
+ <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+ <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+ <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+ <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
<java-symbol type="string" name="selected" />
<java-symbol type="string" name="sendText" />
<java-symbol type="string" name="sending" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 513e022..1b25d7f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -208,6 +208,7 @@
"testng",
],
srcs: [
+ "src/android/app/ActivityManagerTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
new file mode 100644
index 0000000..d930e4d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+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.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testSimple() throws Exception {
+ assertTrue(ActivityManager.isSystemReady());
+ assertFalse(ActivityManager.isUserAMonkey());
+ assertNotEquals(UserHandle.USER_NULL, ActivityManager.getCurrentUser());
+ }
+
+ @Test
+ public void testCapabilities() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.getCapabilitiesSummary(~0));
+ ActivityManager.printCapabilitiesFull(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new StringBuilder(), ~0);
+ }
+
+ @Test
+ public void testProcState() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
+ assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
+ assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateConsideredInteraction(PROCESS_STATE_SERVICE));
+ }
+
+ @Test
+ public void testStartResult() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertTrue(ActivityManager.isStartResultSuccessful(50));
+ assertTrue(ActivityManager.isStartResultFatalError(-50));
+ }
+
+ @Test
+ public void testRestrictionLevel() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.restrictionLevelToName(
+ ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ }
+}
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
deleted file mode 100644
index c8be191..0000000
--- a/core/tests/coretests/src/android/ddm/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-michschn@google.com
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 0bac1c7..1ad71da 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -28,15 +28,20 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class HandlerThreadTest {
private static final int TEST_WHAT = 1;
- @Rule
+ @Rule(order = 1)
+ public ExpectedException mThrown = ExpectedException.none();
+
+ @Rule(order = 2)
public final RavenwoodRule mRavenwood = new RavenwoodRule();
private boolean mGotMessage = false;
@@ -112,4 +117,28 @@
assertTrue(mGotMessage);
assertEquals(TEST_WHAT, mGotMessageWhat);
}
+
+ /**
+ * Confirm that a background handler thread throwing an exception during a test results in a
+ * test failure being reported.
+ */
+ @Test
+ public void testUncaughtExceptionFails() throws Exception {
+ // For the moment we can only test Ravenwood; on a physical device uncaught exceptions
+ // are detected, but reported as test failures at a higher level where we can't inspect
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+ mThrown.expect(IllegalStateException.class);
+
+ final HandlerThread thread = new HandlerThread("HandlerThreadTest");
+ thread.start();
+ thread.getThreadHandler().post(() -> {
+ throw new IllegalStateException();
+ });
+
+ // Wait until we've drained past the message above, then terminate test without throwing
+ // directly; the test harness should notice and report the uncaught exception
+ while (!thread.getThreadHandler().getLooper().getQueue().isIdle()) {
+ SystemClock.sleep(10);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
new file mode 100644
index 0000000..f5a81c5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VibrationAttributesTest {
+ @Test
+ public void testSimple() throws Exception {
+ final VibrationAttributes attr = new VibrationAttributes.Builder()
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .setUsage(VibrationAttributes.USAGE_ALARM)
+ .build();
+
+ assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
+ assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
+ }
+}
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/view/ViewDebugTest.java
similarity index 97%
rename from core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
rename to core/tests/coretests/src/android/view/ViewDebugTest.java
index 7248983..4522842 100644
--- a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
+++ b/core/tests/coretests/src/android/view/ViewDebugTest.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package android.ddm;
+package android.view;
-import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
-import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+import static android.view.ViewDebug.deserializeMethodParameters;
+import static android.view.ViewDebug.serializeReturnValue;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
-import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
import android.platform.test.annotations.Presubmit;
+import android.view.ViewDebug.ViewMethodInvocationSerializationException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -39,7 +39,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
-public final class DdmHandleViewDebugTest {
+public final class ViewDebugTest {
// true
private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..60769c7 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
@@ -475,8 +476,9 @@
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
View view = new View(sContext);
attachViewToWindow(view);
ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +509,9 @@
* <7%: FRAME_RATE_CATEGORY_LOW
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +537,9 @@
* >=7% : FRAME_RATE_CATEGORY_NORMAL
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +563,96 @@
}
/**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_HIGH
+ * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_HIGH
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
+ /**
* Test how values of the frame rate cateogry are aggregated.
* It should take the max value among all of the voted categories per frame.
*/
@@ -701,6 +795,61 @@
});
}
+ /**
+ * Test the logic of infrequent layer:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+ final long delay = 200L;
+
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ // Frequent update
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // In transistion from frequent update to infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // Infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2873428..91e620c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +572,8 @@
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030..50a58da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -465,7 +465,7 @@
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
+ public void cleanupViews() {
cleanupViews(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index aea3ca1..d0db708 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -730,7 +730,7 @@
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
@@ -1714,8 +1714,7 @@
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 127c7e8..dbfa260 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
@@ -41,6 +40,7 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.RemovedBubble;
@@ -427,7 +427,7 @@
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -1069,7 +1069,6 @@
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7..dc27133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -186,6 +187,7 @@
}
if (mTaskView != null) {
mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
mTaskView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 00d683e..73a9cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -266,13 +266,8 @@
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- }
mMenuViewController.hideMenu(false /* animated */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce80..b95d258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,44 @@
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble) {
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(bubble::cleanupViews);
+ } else {
+ bubble.cleanupViews();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ };
if (mDragController != null && mDragController.isStuckToDismiss()) {
- mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+ mAnimationHelper.animateDismiss(runnable);
} else {
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ mAnimationHelper.animateCollapse(runnable);
}
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 0000000..fd91ac0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+}
\ No newline at end of file
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 a089e81..e872849 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
@@ -100,6 +100,9 @@
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
@@ -148,6 +151,8 @@
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -240,6 +245,40 @@
}
}
+ /** Move a task with given `taskId` to desktop without decor */
+ fun moveToDesktopWithoutDecor(
+ taskId: Int,
+ wct: WindowContainerTransaction
+ ): Boolean {
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+ moveToDesktopWithoutDecor(task, wct)
+ return true
+ }
+
+ /**
+ * Move a task to desktop without decor
+ */
+ private fun moveToDesktopWithoutDecor(
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+ task.taskId
+ )
+ exitSplitIfApplicable(wct, task)
+ // Bring other apps to front first
+ bringDesktopAppsToFront(task.displayId, wct)
+ addMoveToDesktopChanges(wct, task)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* Move a task to desktop
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a3..0c25f27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,6 +18,7 @@
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
@@ -58,6 +59,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
mDataSource.trace(ctx -> {
final int handlerId = getHandlerId(handler, ctx);
@@ -97,6 +107,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +139,19 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
- os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
SystemClock.elapsedRealtimeNanos());
os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +170,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -157,6 +194,15 @@
}
private void onFlush() {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
+ try {
+ doOnFlush();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doOnFlush() {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
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 a8b39c41..891eea0 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
@@ -888,7 +888,10 @@
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 75965d6..1668e37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -143,7 +143,7 @@
bubbleController,
mainExecutor
)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@Test
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 295f4dc..e5bdeee 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -763,41 +763,41 @@
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
- {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
- {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
- {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
- {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
+ {"nGetNativeFinalizer", "()J", (void*)CanvasJNI::getNativeFinalizer},
+ {"nFreeCaches", "()V", (void*)CanvasJNI::freeCaches},
+ {"nFreeTextLayoutCaches", "()V", (void*)CanvasJNI::freeTextLayoutCaches},
+ {"nSetCompatibilityVersion", "(I)V", (void*)CanvasJNI::setCompatibilityVersion},
- // ------------ @FastNative ----------------
- {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster},
- {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap},
- {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+ // ------------ @FastNative ----------------
+ {"nInitRaster", "(J)J", (void*)CanvasJNI::initRaster},
+ {"nSetBitmap", "(JJ)V", (void*)CanvasJNI::setBitmap},
+ {"nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*)CanvasJNI::getClipBounds},
- // ------------ @CriticalNative ----------------
- {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
- {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
- {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
- {"nSave","(JI)I", (void*) CanvasJNI::save},
- {"nSaveLayer","(JFFFFJ)I", (void*) CanvasJNI::saveLayer},
- {"nSaveLayerAlpha","(JFFFFI)I", (void*) CanvasJNI::saveLayerAlpha},
- {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer},
- {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer},
- {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
- {"nRestore","(J)Z", (void*) CanvasJNI::restore},
- {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
- {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
- {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
- {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
- {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
- {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
- {"nScale","(JFF)V", (void*) CanvasJNI::scale},
- {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
- {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
- {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
- {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
- {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
- {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
- {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
+ // ------------ @CriticalNative ----------------
+ {"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+ {"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
+ {"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
+ {"nSave", "(JI)I", (void*)CanvasJNI::save},
+ {"nSaveLayer", "(JFFFFJ)I", (void*)CanvasJNI::saveLayer},
+ {"nSaveLayerAlpha", "(JFFFFI)I", (void*)CanvasJNI::saveLayerAlpha},
+ {"nSaveUnclippedLayer", "(JIIII)I", (void*)CanvasJNI::saveUnclippedLayer},
+ {"nRestoreUnclippedLayer", "(JIJ)V", (void*)CanvasJNI::restoreUnclippedLayer},
+ {"nGetSaveCount", "(J)I", (void*)CanvasJNI::getSaveCount},
+ {"nRestore", "(J)Z", (void*)CanvasJNI::restore},
+ {"nRestoreToCount", "(JI)V", (void*)CanvasJNI::restoreToCount},
+ {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
+ {"nSetMatrix", "(JJ)V", (void*)CanvasJNI::setMatrix},
+ {"nConcat", "(JJ)V", (void*)CanvasJNI::concat},
+ {"nConcat", "(J[F)V", (void*)CanvasJNI::concat44},
+ {"nRotate", "(JF)V", (void*)CanvasJNI::rotate},
+ {"nScale", "(JFF)V", (void*)CanvasJNI::scale},
+ {"nSkew", "(JFF)V", (void*)CanvasJNI::skew},
+ {"nTranslate", "(JFF)V", (void*)CanvasJNI::translate},
+ {"nQuickReject", "(JJ)Z", (void*)CanvasJNI::quickRejectPath},
+ {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+ {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
+ {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+ {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
};
// If called from Canvas these are regular JNI
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index e4dc152..0613fc6 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -15,6 +15,9 @@
*/
package android.media;
+import static android.media.audio.Flags.FLAG_SCO_MANAGED_BY_AUDIO;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothProfile;
@@ -174,4 +177,13 @@
public boolean isLeOutput() {
return mIsLeOutput;
}
+
+ /**
+ * Factory method for <code>BluetoothProfileConnectionInfo</code> for an HFP device.
+ */
+ @FlaggedApi(FLAG_SCO_MANAGED_BY_AUDIO)
+ public static @NonNull BluetoothProfileConnectionInfo createHfpInfo() {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.HEADSET, false,
+ -1, false);
+ }
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 1046d8e9..9742d46 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,10 +64,8 @@
}
public final class NfcAdapter {
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
method public void disableForegroundDispatch(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
@@ -83,6 +81,7 @@
method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setTransactionAllowed(boolean);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 4d56c11..55506a1 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1204,37 +1204,21 @@
}
}
- /**
- * Disables observe mode to allow the transaction to proceed. See
- * {@link #isObserveModeSupported()} for a description of observe mode and
- * use {@link #disallowTransaction()} to enable observe mode and block
- * transactions again.
- *
- * @return boolean indicating success or failure.
- */
- @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean allowTransaction() {
- try {
- return sService.setObserveMode(false);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
- }
- }
-
/**
- * Signals that the transaction has completed and observe mode may be
- * reenabled. See {@link #isObserveModeSupported()} for a description of
- * observe mode and use {@link #allowTransaction()} to disable observe
- * mode and allow transactions to proceed.
- *
- * @return boolean indicating success or failure.
- */
+ * Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
+ * and simply observe and notify the APDU service of polling loop frames. See
+ * {@link #isObserveModeSupported()} for a description of observe mode.
+ *
+ * @param allowed true disables observe mode to allow the transaction to proceed while false
+ * enables observe mode and does not allow transactions to proceed.
+ *
+ * @return boolean indicating success or failure.
+ */
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean disallowTransaction() {
+ public boolean setTransactionAllowed(boolean allowed) {
try {
- return sService.setObserveMode(true);
+ return sService.setObserveMode(!allowed);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 1570e0e..31104a0 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -46,14 +46,6 @@
_nativeClassInit();
}
- // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
-
- public static native boolean eglPresentationTimeANDROID(
- EGLDisplay dpy,
- EGLSurface sur,
- long time
- );
-
/**
* Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
*
@@ -83,4 +75,13 @@
}
private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
+ // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+ public static native boolean eglPresentationTimeANDROID(
+ EGLDisplay dpy,
+ EGLSurface sur,
+ long time
+ );
+
}
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 27ddff9..63e6c50 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -3,7 +3,15 @@
srcs: [
"java/**/*.java",
"java/**/*.aidl",
+ ":statslog-crashrecovery-java-gen",
],
- path: "java",
visibility: ["//frameworks/base:__subpackages__"],
}
+
+genrule {
+ name: "statslog-crashrecovery-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module crashrecovery " +
+ "--javaPackage com.android.server.crashrecovery.proto --javaClass CrashRecoveryStatsLog --worksource",
+ out: ["com/android/server/crashrecovery/proto/CrashRecoveryStatsLog.java"],
+}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dd54334..dffe4e2 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -46,11 +46,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import java.io.File;
import java.util.ArrayList;
@@ -390,7 +390,7 @@
return;
}
- FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+ CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079..50322f0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -39,13 +39,13 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.SystemConfig;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import com.android.server.pm.ApexManager;
import java.io.BufferedReader;
@@ -418,7 +418,7 @@
final VersionedPackage logPackage = logPackageTemp;
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
reasonToLog, failedPackageToLog);
Consumer<Intent> onResult = result -> {
@@ -430,19 +430,19 @@
int rollbackId = rollback.getRollbackId();
saveStagedRollbackId(rollbackId, logPackage);
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
reasonToLog, failedPackageToLog);
} else {
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
reasonToLog, failedPackageToLog);
}
} else {
WatchdogRollbackLogger.logEvent(logPackage,
- FrameworkStatsLog
+ CrashRecoveryStatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
reasonToLog, failedPackageToLog);
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index f9ef994..898c543 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -16,16 +16,16 @@
package com.android.server.rollback;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,8 +42,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import java.util.List;
import java.util.Set;
@@ -197,8 +197,8 @@
+ " rollbackReason: " + rollbackReasonToString(rollbackReason)
+ " failedPackageName: " + failingPackageName);
if (logPackage != null) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
type,
logPackage.getPackageName(),
logPackage.getVersionCode(),
@@ -208,8 +208,8 @@
} else {
// In the case that the log package is null, still log an empty string as an
// indication that retrieving the logging parent failed.
- FrameworkStatsLog.write(
- FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ CrashRecoveryStatsLog.write(
+ CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
type,
"",
0,
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9..f425f52 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
<!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
after parts of the app have been previously moved into the cloud for temporary storage.
"installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
- <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+ <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
<!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
<string name="unarchive_body_text">This app will begin to download in the background</string>
<!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c..b20117d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Process;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@
String appTitle = pm.getApplicationInfo(mPackageName,
PackageManager.ApplicationInfoFlags.of(
MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
- // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
- // archived apps.
- showDialogFragment(appTitle, "installerTitle");
+ String installerTitle = getResponsibleInstallerTitle(pm,
+ pm.getInstallSourceInfo(mPackageName));
+ showDialogFragment(appTitle, installerTitle);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Invalid packageName: " + e.getMessage());
}
}
+ private String getResponsibleInstallerTitle(PackageManager pm,
+ InstallSourceInfo installSource)
+ throws PackageManager.NameNotFoundException {
+ String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+ ? installSource.getInstallingPackageName()
+ : installSource.getUpdateOwnerPackageName();
+ if (packageName == null) {
+ // Should be unreachable.
+ Log.e(TAG, "Installer not found.");
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return "";
+ }
+ return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+ }
+
@NonNull
private String[] getRequestedPermissions(String callingPackage) {
String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb3..6761aa7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
package com.android.settingslib.volume.data.repository
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
interface AudioRepository {
/** Current [AudioManager.getMode]. */
val mode: StateFlow<Int>
+
+ /**
+ * Ringtone mode.
+ *
+ * @see AudioManager.getRingerModeInternal
+ */
+ val ringerMode: StateFlow<RingerMode>
+
+ /**
+ * Communication device. Emits null when there is no communication device available.
+ *
+ * @see AudioDeviceInfo.getType
+ */
+ val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+ /** State of the [AudioStream]. */
+ suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+ /** Current state of the [AudioStream]. */
+ suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
}
class AudioRepositoryImpl(
+ private val context: Context,
private val audioManager: AudioManager,
- backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+ private val coroutineScope: CoroutineScope,
) : AudioRepository {
override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@
}
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+ private val audioManagerIntents: SharedFlow<String> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ intent.action?.let { action -> launch { send(action) } }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ for (action in allActions) {
+ addAction(action)
+ }
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+ override val ringerMode: StateFlow<RingerMode> =
+ audioManagerIntents
+ .filter { ringerActions.contains(it) }
+ .map { RingerMode(audioManager.ringerModeInternal) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ RingerMode(audioManager.ringerModeInternal),
+ )
+
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() =
+ callbackFlow {
+ val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+ audioManager.addOnCommunicationDeviceChangedListener(
+ DirectExecutor.INSTANCE,
+ listener
+ )
+
+ awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+ }
+ .filterNotNull()
+ .map { audioManager.communicationDevice }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ audioManager.communicationDevice,
+ )
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ return audioManagerIntents
+ .filter { modelActions.contains(it) }
+ .map { getCurrentAudioStream(audioStream) }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return withContext(backgroundCoroutineContext) {
+ AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode =
+ audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value)
+ )
+ }
+ }
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ withContext(backgroundCoroutineContext) {
+ audioManager.setStreamVolume(audioStream.value, volume, 0)
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ withContext(backgroundCoroutineContext) {
+ if (isMuted) {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+ } else {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+ }
+ }
+
+ private fun getMinVolume(stream: AudioStream): Int =
+ try {
+ audioManager.getStreamMinVolume(stream.value)
+ } catch (e: IllegalArgumentException) {
+ // Fallback to STREAM_VOICE_CALL because
+ // CallVolumePreferenceController.java default
+ // return STREAM_VOICE_CALL in getAudioStream
+ audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+ }
+
+ private companion object {
+ val modelActions =
+ setOf(
+ AudioManager.STREAM_MUTE_CHANGED_ACTION,
+ AudioManager.MASTER_MUTE_CHANGED_ACTION,
+ AudioManager.VOLUME_CHANGED_ACTION,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ )
+ val ringerActions =
+ setOf(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ )
+ val allActions = ringerActions + modelActions
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 0000000..2b12936
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+ val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+ override val isSingleVolume: Boolean
+ get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 0000000..58f3c2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+ init {
+ require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedStreamTypes =
+ setOf(
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_BLUETOOTH_SCO,
+ AudioManager.STREAM_SYSTEM_ENFORCED,
+ AudioManager.STREAM_DTMF,
+ AudioManager.STREAM_TTS,
+ AudioManager.STREAM_ACCESSIBILITY,
+ AudioManager.STREAM_ASSISTANT,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 0000000..c1be1ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+ val audioStream: AudioStream,
+ val volume: Int,
+ val minVolume: Int,
+ val maxVolume: Int,
+ val isAffectedByRingerMode: Boolean,
+ val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 0000000..9f03927
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+ init {
+ require(value in supportedRingerModes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedRingerModes =
+ setOf(
+ AudioManager.RINGER_MODE_SILENT,
+ AudioManager.RINGER_MODE_VIBRATE,
+ AudioManager.RINGER_MODE_NORMAL,
+ AudioManager.RINGER_MODE_MAX,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
"android.test.runner",
"telephony-common",
"android.test.base",
+ "android.test.mock",
+ "truth",
],
platform_apis: true,
@@ -49,16 +51,23 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"flag-junit",
- "mockito-target-minus-junit4",
+ "kotlinx_coroutines_test",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
- "kotlinx_coroutines_test",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
dxflags: ["--multi-dex"],
+ manifest: "AndroidManifest.xml",
}
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Runs Tests for SettingsLib.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsLibTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+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.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor
+ private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+ @Captor
+ private lateinit var communicationDeviceListenerCaptor:
+ ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+ private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+ private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val testScope = TestScope()
+
+ private lateinit var underTest: AudioRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+ `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+ `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+ `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+ triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ }
+ `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ }
+ `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+ volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+ }
+ `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+ isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+ `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+ isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+
+ underTest =
+ AudioRepositoryImpl(
+ context,
+ audioManager,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun audioModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<Int>()
+ underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerModeChange(AudioManager.MODE_IN_CALL)
+ runCurrent()
+
+ assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+ }
+ }
+
+ @Test
+ fun ringerModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<RingerMode>()
+ underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ runCurrent()
+
+ assertThat(modes)
+ .containsExactly(
+ RingerMode(AudioManager.RINGER_MODE_NORMAL),
+ RingerMode(AudioManager.RINGER_MODE_SILENT),
+ )
+ }
+ }
+
+ @Test
+ fun communicationDeviceChanges_repositoryEmits() {
+ testScope.runTest {
+ var device: AudioDeviceInfo? = null
+ underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerConnectedDeviceChange(communicationDevice)
+ runCurrent()
+
+ assertThat(device).isSameInstanceAs(communicationDevice)
+ }
+ }
+
+ @Test
+ fun adjustingVolume_changesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 50,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustingVolume_currentModeIsUpToDate() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+ }
+ }
+
+ @Test
+ fun muteStream_mutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun unmuteStream_unmutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ isMuteByStream[audioStream.value] = true
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+ verify(audioManager)
+ .addOnCommunicationDeviceChangedListener(
+ any(),
+ communicationDeviceListenerCaptor.capture(),
+ )
+ communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+ }
+
+ private fun triggerModeChange(mode: Int) {
+ verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+ modeListenerCaptor.value.onModeChanged(mode)
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ const val MIN_VOLUME = 0
+ const val MAX_VOLUME = 100
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
package com.android.settingslib.volume.data.repository
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
@@ -26,7 +32,59 @@
override val mode: StateFlow<Int>
get() = mutableMode.asStateFlow()
+ private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ override val ringerMode: StateFlow<RingerMode>
+ get() = mutableRingerMode.asStateFlow()
+
+ private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() = mutableCommunicationDevice.asStateFlow()
+
+ private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+ private fun getAudioStreamModelState(
+ audioStream: AudioStream
+ ): MutableStateFlow<AudioStreamModel> =
+ models.getOrPut(audioStream) {
+ MutableStateFlow(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = 0,
+ maxVolume = 0,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ getAudioStreamModelState(audioStream).asStateFlow()
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+ getAudioStreamModelState(audioStream).value
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ }
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
+
+ fun setRingerMode(newRingerMode: RingerMode) {
+ mutableRingerMode.value = newRingerMode
+ }
+
+ fun setCommunicationDevice(device: AudioDeviceInfo?) {
+ mutableCommunicationDevice.value = device
+ }
+
+ fun setAudioStreamModel(model: AudioStreamModel) {
+ getAudioStreamModelState(model.audioStream).update { model }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
package com.android.settingslib.volume.domain.interactor
import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
import com.android.settingslib.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index 6d6e2ff..e2eda4f 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,5 +32,7 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"truth",
+ "kotlinx_coroutines_test",
+ "mockito-target-minus-junit4",
],
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
<!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
<uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+ <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3db99f28..ba3026e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,16 @@
}
flag {
+ name: "nssl_falsing_fix"
+ namespace: "systemui"
+ description: "Minor touch changes to prevent falsing errors in NSSL"
+ bug: "316551193"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "refactor_get_current_user"
namespace: "systemui"
description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
@@ -94,7 +104,7 @@
" standard background color is desired. This was the behavior before we discovered"
" a resources threading issue, which we worked around by tinting the notification"
" backgrounds and footer buttons."
- bug: "294347738"
+ bug: "294830092"
}
flag {
@@ -323,13 +333,6 @@
}
flag {
- name: "screenshare_notification_hiding"
- namespace: "systemui"
- description: "Enable hiding of notifications during screenshare"
- bug: "312784809"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -365,9 +368,9 @@
}
flag {
- name: "enable_keyguard_compose"
+ name: "compose_lockscreen"
namespace: "systemui"
- description: "Enables the compose version of keyguard."
+ description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
bug: "301968149"
}
@@ -384,3 +387,11 @@
description: "Enables on-screen contextual tip about how to take screenshot."
bug: "322891421"
}
+
+flag {
+ name: "shaderlib_loading_effect_refactor"
+ namespace: "systemui"
+ description: "Extend shader library to provide the common loading effects."
+ bug: "282007590"
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 68e57b5..071433e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -53,7 +53,7 @@
stickyKeys.forEach { (key, isLocked) ->
key(key) {
Text(
- text = key.text,
+ text = key.displayedText,
fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
)
}
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 5258078..d904c8b 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
@@ -504,6 +504,7 @@
layoutImpl.density = density
layoutImpl.swipeSourceDetector = swipeSourceDetector
+ layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0ec..2dc94a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +49,7 @@
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
private class TestGestureScope(
- val coroutineScope: MonotonicClockTestScope,
+ private val testScope: MonotonicClockTestScope,
) {
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = coroutineScope,
+ coroutineScope = testScope,
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
- val draggable = sceneGestureHandler.draggable
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
SceneNestedScrollHandler(
- layoutImpl,
+ layoutImpl = layoutImpl,
orientation = sceneGestureHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@
fun up(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
- // Offset y: 10% of the screen
- val offsetY10 = Offset(x = 0f, y = down(0.1f))
+ fun downOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("upOffset() is required, not implemented yet")
+ } else {
+ Offset(x = 0f, y = down(fractionOfScreen))
+ }
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -127,15 +132,15 @@
get() = (transitionState as Transition).progress
fun advanceUntilIdle() {
- coroutineScope.testScheduler.advanceUntilIdle()
+ testScope.testScheduler.advanceUntilIdle()
}
fun runCurrent() {
- coroutineScope.testScheduler.runCurrent()
+ testScope.testScheduler.runCurrent()
}
fun assertIdle(currentScene: SceneKey) {
- assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+ assertThat(transitionState).isInstanceOf(Idle::class.java)
assertWithMessage("currentScene does not match")
.that(transitionState.currentScene)
.isEqualTo(currentScene)
@@ -148,11 +153,8 @@
progress: Float? = null,
isUserInputOngoing: Boolean? = null
) {
- val transition = transitionState
- assertWithMessage("transitionState must be Transition")
- .that(transition is Transition)
- .isTrue()
- transition as Transition
+ assertThat(transitionState).isInstanceOf(Transition::class.java)
+ val transition = transitionState as Transition
if (currentScene != null)
assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@
.that(transition.isUserInputOngoing)
.isEqualTo(isUserInputOngoing)
}
+
+ fun onDragStarted(
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float,
+ pointersDown: Int = 1
+ ) {
+ // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+ if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+ onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ }
+
+ fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+ onDragStarted(
+ sceneGestureHandler.draggable,
+ startedPosition,
+ overSlop = 0f,
+ pointersDown
+ )
+ }
+
+ fun onDragStarted(
+ draggableHandler: DraggableHandler,
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float = 0f,
+ pointersDown: Int = 1
+ ) {
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
+
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(pixels = overSlop)
+ }
+
+ fun onDelta(pixels: Float) {
+ sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ }
+
+ fun onDragStopped(velocity: Float) {
+ sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+ runCurrent()
+ }
+
+ fun NestedScrollConnection.scroll(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ onPreScroll(
+ available = available,
+ source = NestedScrollSource.Drag,
+ )
+ val consumed = consumedByPreScroll + consumedByScroll
+
+ onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ fun NestedScrollConnection.preFling(
+ available: Velocity,
+ coroutineScope: CoroutineScope = testScope,
+ ) {
+ // onPreFling is a suspend function that returns the consumed velocity once it finishes
+ // consuming it. In the current scenario, it returns after completing the animation.
+ // To return immediately, we can initiate a job that allows us to check the status
+ // before the animation starts.
+ coroutineScope.launch { onPreFling(available = available) }
+ runCurrent()
+ }
}
private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
- runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
- }
+ runMonotonicClockTest {
+ val testGestureScope = TestGestureScope(testScope = this)
- private fun DraggableHandler.onDragStarted(
- overSlop: Float = 0f,
- startedPosition: Offset = Offset.Zero,
- ) {
- onDragStarted(startedPosition, overSlop)
- // MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(overSlop)
+ // run the test
+ testGestureScope.block()
+ }
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- draggable.onDelta(pixels = down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold - 0.01f,
- )
+ onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -230,11 +300,10 @@
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = velocityThreshold)
-
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -244,10 +313,10 @@
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = 0f)
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -255,7 +324,7 @@
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted(overSlop = -60f)
+ onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -264,7 +333,7 @@
)
// Reverse direction such that A -> C now with 0.4
- draggable.onDelta(100f)
+ onDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -273,7 +342,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -283,9 +352,12 @@
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+ onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
- horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+ onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
}
@@ -294,7 +366,7 @@
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- draggable.onDragStarted(10f)
+ onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -303,7 +375,7 @@
)
// Reverse drag direction, it will consume the previous drag
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -312,7 +384,7 @@
)
// Continue reverse drag direction, it should record progress to Scene B
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -326,7 +398,10 @@
navigateToSceneC()
// Start dragging from the bottom
- draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ overSlop = up(fractionOfScreen = 0.1f)
+ )
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -337,14 +412,14 @@
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- draggable.onDragStarted(down(0.3f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- draggable.onDelta(up(0.3f))
+ onDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -355,8 +430,8 @@
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- draggable.onDragStarted(down(1f))
- draggable.onDragStopped(0f)
+ onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -364,7 +439,7 @@
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted(overSlop = up(0.2f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -373,13 +448,13 @@
)
// Start animation A -> B with progress 0.2 -> 1.0
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -388,7 +463,7 @@
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -398,9 +473,9 @@
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = up(0.2f))
- draggable.onDelta(up(0.2f))
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ onDelta(pixels = up(fractionOfScreen = 0.2f))
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- draggable.onDragStarted()
- draggable.onDelta(up(0.5f))
- draggable.onDelta(up(0.5f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- draggable.onDragStopped(down(0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
- draggable.onDragStopped(down(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(overSlop = 0f)
- draggable.onDelta(up(0.1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold,
- )
+ onDragStopped(velocity = velocityThreshold)
- // The stop animation is not started yet
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
- runCurrent()
-
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
// Start a new gesture while the offset is animating
- draggable.onDragStarted()
+ onDragStartedImmediately()
assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertIdle(currentScene = SceneA)
}
@@ -509,40 +580,29 @@
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
- available = offsetY10,
+ available = downOffset(fractionOfScreen = 0.1f),
source = NestedScrollSource.Drag
)
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- assertThat(consumed).isEqualTo(offsetY10)
- }
-
- private fun NestedScrollConnection.scroll(
- available: Offset,
- consumedByScroll: Offset = Offset.Zero,
- ) {
- val consumedByPreScroll =
- onPreScroll(available = available, source = NestedScrollSource.Drag)
- val consumed = consumedByPreScroll + consumedByScroll
- onPostScroll(
- consumed = consumed,
- available = available - consumed,
- source = NestedScrollSource.Drag
- )
+ assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
}
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
// start intercept preScroll
val consumed =
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertThat(progress).isEqualTo(0.2f)
// do nothing on postScroll
@@ -553,12 +613,12 @@
)
assertThat(progress).isEqualTo(0.2f)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.3f)
assertTransition(currentScene = SceneA)
}
- private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+ private fun TestGestureScope.preScrollAfterSceneTransition(
firstScroll: Float,
secondScroll: Float
) {
@@ -567,10 +627,13 @@
nestedScroll.scroll(available = Offset(0f, firstScroll))
// stop scene transition (start the "stop animation")
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
// a pre scroll event, that could be intercepted by SceneGestureHandler
- nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = Offset(0f, secondScroll),
+ source = NestedScrollSource.Drag
+ )
}
@Test
@@ -610,16 +673,17 @@
preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
+ advanceUntilIdle()
assertIdle(SceneB)
}
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -627,15 +691,15 @@
assertIdle(currentScene = SceneA)
}
- private suspend fun TestGestureScope.flingAfterScroll(
+ private fun TestGestureScope.flingAfterScroll(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -679,19 +743,22 @@
}
/** we started the scroll in the scene, then fling with the velocityThreshold */
- private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+ private fun TestGestureScope.flingAfterScrollStartedInScene(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
// scroll consumed in child
- nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+ nestedScroll.scroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+ )
// scroll offsetY10 is all available for parents
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -732,20 +799,20 @@
@Test
fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- draggable.onDelta(down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertIdle(currentScene = SceneA)
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertIdle(currentScene = SceneA)
}
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@@ -753,8 +820,10 @@
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
// Start a drag and then stop it, given that
- draggable.onDragStarted(overSlop = up(0.1f))
+ onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(-velocityThreshold)
+ onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@
nestedScroll.scroll(available = -offsetY10)
assertThat(progress).isEqualTo(0.4f)
- nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
// wait for the stop animation
@@ -788,7 +857,7 @@
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -802,7 +871,7 @@
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
- draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+ onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b2..0245cf2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -12,16 +14,38 @@
* function.
*
* The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
- // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
- withContext(TestMonotonicFrameClock(this)) {
- MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ val testScope: TestScope = this
+
+ withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+ val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+ val scope =
+ MonotonicClockTestScope(
+ testScope = testScopeWithMonotonicFrameClock,
+ testScheduler = testScope.testScheduler,
+ backgroundScope = backgroundScope,
+ )
+
+ // Run the test
+ scope.block()
}
}
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ * in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
class MonotonicClockTestScope(
- coroutineScope: CoroutineScope,
- val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+ testScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler,
+ val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105..e39d7ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
+ val isNotificationHistoryEnabled: Flow<Boolean> =
+ secureSettingsRepository
+ .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+ .map { it == 1 }
+ .distinctUntilChanged()
+
/** The current state of the notification setting. */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8..04e8090 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
+ val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
/** Should notifications be visible on the lockscreen? */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
repository.isShowNotificationsOnLockScreenEnabled
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 1642e52..45f98be 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
@@ -72,7 +72,7 @@
testScope.runTest {
val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
@Test
@@ -84,16 +84,16 @@
// Initial value is false.
val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
}
@@ -104,20 +104,20 @@
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
val mediaModel = collectLastValue(underTest.mediaModel)
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
// Media active now returns false.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index efccf7a..e4ce6cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -17,7 +17,7 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.Kosmos
@@ -38,4 +38,3 @@
val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
-val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index ce74a90..6ad32cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -27,13 +27,14 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -41,6 +42,9 @@
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -48,6 +52,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class HomeControlsComponentInteractorTest : SysuiTestCase() {
@@ -59,20 +64,20 @@
private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
private lateinit var underTest: HomeControlsComponentInteractor
private lateinit var userRepository: FakeUserRepository
- private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+ private lateinit var selectedComponentRepository: SelectedComponentRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- userRepository = kosmos.fakeUserRepository
- userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
controlsComponent = kosmos.controlsComponent
authorizedPanelsRepository = kosmos.authorizedPanelsRepository
controlsListingController = kosmos.controlsListingController
selectedComponentRepository = kosmos.selectedComponentRepository
- selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -90,14 +95,13 @@
fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -105,16 +109,15 @@
fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
whenever(controlsListingController.getCurrentServices())
.thenReturn(
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
)
val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -122,9 +125,8 @@
fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -137,8 +139,8 @@
fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -151,17 +153,24 @@
fun testPanelComponentReturnsComponentNameForDifferentUsers() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(ANOTHER_USER)
+ val actualValue by collectLastValue(underTest.panelComponent)
+
+ // Secondary user has non-panel selected.
+ setActiveUser(ANOTHER_USER)
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
- selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+
+ // Primary user has panel selected.
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
- val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
+
+ // Back to secondary user, should be null.
+ setActiveUser(ANOTHER_USER)
+ runServicesUpdate()
+ assertThat(actualValue).isNull()
}
}
@@ -169,8 +178,7 @@
fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.empty())
userRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -182,11 +190,17 @@
private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
val listings =
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
callback.onServicesUpdated(listings)
}
+ private suspend fun TestScope.setActiveUser(user: UserInfo) {
+ userRepository.setSelectedUserInfo(user)
+ kosmos.fakeUserTracker.set(listOf(user), 0)
+ runCurrent()
+ }
+
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
@@ -237,19 +251,9 @@
)
private const val TEST_PACKAGE = "pkg"
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
- private const val TEST_PACKAGE_PANEL = "pkg.panel"
- private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
private val TEST_SELECTED_COMPONENT_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- true
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
private val TEST_SELECTED_COMPONENT_NON_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- false
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 6610e70..87b1bbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,8 +85,7 @@
userRepository.setUserInfos(listOf(PRIMARY_USER))
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
index 2fe4ef78..f400cb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -25,9 +25,11 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
@@ -146,6 +148,7 @@
kosmos.fakeKeyguardRepository.setIsDozing(false)
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.DeviceEntryAuthentication,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
)
)
@@ -165,6 +168,7 @@
kosmos.fakeKeyguardRepository.setIsDozing(true)
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.DeviceEntryAuthentication,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
)
)
@@ -177,6 +181,7 @@
private fun createViewModel() =
SideFpsProgressBarViewModel(
kosmos.applicationContext,
+ kosmos.biometricStatusInteractor,
kosmos.deviceEntryFingerprintAuthInteractor,
kosmos.sideFpsSensorInteractor,
kosmos.dozeServiceHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index d6d2509..189ba7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -522,14 +522,18 @@
}
@Test
- fun factoryResetProtectionActive_isNotVisible() =
+ fun deviceProvisioningAndFactoryResetProtection() =
testScope.runTest {
val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
- assertThat(isVisible).isTrue()
-
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isActive = true)
-
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(false)
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+ assertThat(isVisible).isTrue()
}
/**
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 1abbc92..12dbf11 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
@@ -49,6 +49,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
@@ -164,6 +165,30 @@
}
@Test
+ fun hydrateVisibility_basedOnDeviceProvisioningAndFactoryResetProtection() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ isDeviceProvisioned = false,
+ isFrpActive = true,
+ )
+
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+ assertThat(isVisible).isTrue()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
fun startsInLockscreenScene() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
@@ -745,6 +770,8 @@
authenticationMethod: AuthenticationMethodModel? = null,
isLockscreenEnabled: Boolean = true,
startsAwake: Boolean = true,
+ isDeviceProvisioned: Boolean = true,
+ isFrpActive: Boolean = false,
): MutableStateFlow<ObservableTransitionState> {
if (authenticationMethod?.isSecure == true) {
assert(isLockscreenEnabled) {
@@ -781,6 +808,10 @@
} else {
powerInteractor.setAsleepForTest()
}
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isFrpActive)
+
runCurrent()
return transitionStateFlow
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..a877853
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:app="http://schemas.android.com/apk/res-auto"
+xmlns:tools="http://schemas.android.com/tools"
+android:layout_width="match_parent"
+android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/biometric_dialog_empty_space_description"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <View
+ android:id="@+id/panel"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:clickable="true"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp"
+ android:visibility="visible"
+ app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+ app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+ app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+ app:layout_constraintTop_toTopOf="@+id/title" />
+
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+ android:id="@+id/biometric_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.8"
+ tools:srcCompat="@tools:sample/avatars" />
+
+ <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+ android:id="@+id/biometric_icon_overlay"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"
+ app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+ app:layout_constraintTop_toTopOf="@+id/biometric_icon"
+ app:layout_constraintVertical_bias="0.0" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ style="@style/TextAppearance.AuthCredential.Title"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ app:layout_constraintBottom_toTopOf="@+id/description"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <Space
+ android:id="@+id/space_above_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_space_above_content"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+ <ScrollView
+ android:id="@+id/customized_view_container"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:fillViewport="true"
+ android:fadeScrollbars="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+ android:scrollbars="vertical"
+ android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@+id/space_above_content"
+ app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:scrollbars="vertical"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ style="@style/TextAppearance.AuthCredential.Description"
+ app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <TextView
+ android:id="@+id/indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:gravity="center_horizontal"
+ android:textColor="@color/biometric_dialog_gray"
+ android:textSize="12sp"
+ android:accessibilityLiveRegion="polite"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:fadingEdge="horizontal"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="@+id/panel"
+ app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />
+
+ <!-- Negative Button, reserved for app -->
+ <Button
+ android:id="@+id/button_negative"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- Cancel Button, replaces negative button when biometric is accepted -->
+ <Button
+ android:id="@+id/button_cancel"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:text="@string/cancel"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- "Use Credential" Button, replaces if device credential is allowed -->
+ <Button
+ android:id="@+id/button_use_credential"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel" />
+
+ <!-- Positive Button -->
+ <Button
+ android:id="@+id/button_confirm"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginRight="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_confirm"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ tools:visibility="invisible" />
+
+ <!-- Try Again Button -->
+ <Button
+ android:id="@+id/button_try_again"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="8dp"
+ android:layout_marginRight="8dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/panel"
+ app:layout_constraintEnd_toEndOf="@+id/panel" />
+
+ <!-- Guidelines for setting panel border -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/leftGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/rightGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/bottomGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 57e308f..3397906 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.systemui.Flags.constraintBp;
import android.animation.Animator;
import android.annotation.IntDef;
@@ -57,6 +58,7 @@
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -153,7 +155,7 @@
@Nullable private Spaghetti mBiometricView;
@Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
- private final FrameLayout mFrameLayout;
+ private final ViewGroup mLayout;
private final ImageView mBackgroundView;
private final ScrollView mBiometricScrollView;
private final View mPanelView;
@@ -339,11 +341,16 @@
mBiometricCallback = new BiometricCallback();
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- mFrameLayout = (FrameLayout) layoutInflater.inflate(
- R.layout.auth_container_view, this, false /* attachToRoot */);
- addView(mFrameLayout);
- mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
- mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ if (constraintBp()) {
+ mLayout = (ConstraintLayout) layoutInflater.inflate(
+ R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
+ } else {
+ mLayout = (FrameLayout) layoutInflater.inflate(
+ R.layout.auth_container_view, this, false /* attachToRoot */);
+ }
+ mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
+ addView(mLayout);
+ mBackgroundView = mLayout.findViewById(R.id.background);
ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
@@ -358,7 +365,7 @@
}
});
- mPanelView = mFrameLayout.findViewById(R.id.panel);
+ mPanelView = mLayout.findViewById(R.id.panel);
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
@@ -402,20 +409,31 @@
new BiometricModalities(fpProps, faceProps),
config.mOpPackageName);
- final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
- R.layout.biometric_prompt_layout, null, false);
- mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
- // TODO(b/201510778): This uses the wrong timeout in some cases
- getJankListener(view, TRANSIT,
- BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
- mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
- vibratorHelper);
+ if (constraintBp()) {
+ mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
+ // TODO(b/201510778): This uses the wrong timeout in some cases
+ getJankListener(mLayout, TRANSIT,
+ BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper);
+ } else {
+ final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
+ R.layout.biometric_prompt_layout, null, false);
+ mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
+ // TODO(b/201510778): This uses the wrong timeout in some cases
+ getJankListener(view, TRANSIT,
+ BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper);
- // TODO(b/251476085): migrate these dependencies
- if (fpProps != null && fpProps.isAnyUdfpsType()) {
- view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
- config.mScaleProvider);
+ // TODO(b/251476085): migrate these dependencies
+ if (fpProps != null && fpProps.isAnyUdfpsType()) {
+ view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
+ config.mScaleProvider);
+ }
}
+ } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+ addCredentialView(true, false);
} else {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
@@ -477,7 +495,7 @@
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
- mFrameLayout.addView(mCredentialView);
+ mLayout.addView(mCredentialView);
}
@Override
@@ -488,7 +506,9 @@
@Override
public void onOrientationChanged() {
- maybeUpdatePositionForUdfps(true /* invalidate */);
+ if (!constraintBp()) {
+ maybeUpdatePositionForUdfps(true /* invalidate */);
+ }
}
@Override
@@ -502,8 +522,9 @@
mWakefulnessLifecycle.addObserver(this);
mPanelInteractionDetector.enable(
() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
-
- if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
+ if (constraintBp()) {
+ // Do nothing on attachment with constraintLayout
+ } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
mBiometricScrollView.addView(mBiometricView.asView());
} else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -512,7 +533,9 @@
+ mConfig.mPromptInfo.getAuthenticators());
}
- maybeUpdatePositionForUdfps(false /* invalidate */);
+ if (!constraintBp()) {
+ maybeUpdatePositionForUdfps(false /* invalidate */);
+ }
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4176083..4ea5f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
else ->
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index d28dbc0..27bb023 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -24,17 +24,24 @@
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.biometrics.shared.model.AuthenticationState
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
/** A repository for the state of biometric authentication. */
@@ -44,6 +51,9 @@
* [NotRunning].
*/
val fingerprintAuthenticationReason: Flow<AuthenticationReason>
+
+ /** The current status of an acquired fingerprint. */
+ val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
}
@SysUISingleton
@@ -54,53 +64,53 @@
private val biometricManager: BiometricManager?
) : BiometricStatusRepository {
- override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ private val authenticationState: Flow<AuthenticationState> =
conflatedCallbackFlow {
- val updateFingerprintAuthenticateReason = { reason: AuthenticationReason ->
- trySendWithFailureLogging(
- reason,
- TAG,
- "Error sending fingerprintAuthenticateReason reason"
- )
+ val updateAuthenticationState = { state: AuthenticationState ->
+ trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
}
val authenticationStateListener =
object : AuthenticationStateListener.Stub() {
override fun onAuthenticationStarted(requestReason: Int) {
- val authenticationReason =
- when (requestReason) {
- REASON_AUTH_BP ->
- AuthenticationReason.BiometricPromptAuthentication
- REASON_AUTH_KEYGUARD ->
- AuthenticationReason.DeviceEntryAuthentication
- REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
- REASON_AUTH_SETTINGS ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.OTHER
- )
- REASON_ENROLL_ENROLLING ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.ENROLL_ENROLLING
- )
- REASON_ENROLL_FIND_SENSOR ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.ENROLL_FIND_SENSOR
- )
- else -> AuthenticationReason.Unknown
- }
- updateFingerprintAuthenticateReason(authenticationReason)
+ val authenticationReason = requestReason.toAuthenticationReason()
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStarted(authenticationReason)
+ )
}
override fun onAuthenticationStopped() {
- updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStopped(
+ AuthenticationReason.NotRunning
+ )
+ )
}
override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
+
+ override fun onAuthenticationAcquired(
+ biometricSourceType: BiometricSourceType,
+ requestReason: Int,
+ acquiredInfo: Int
+ ) {
+ val authReason = requestReason.toAuthenticationReason()
+
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationAcquired(
+ biometricSourceType,
+ authReason,
+ acquiredInfo
+ )
+ )
+ }
}
- updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStarted(AuthenticationReason.NotRunning)
+ )
biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
awaitClose {
biometricManager?.unregisterAuthenticationStateListener(
@@ -110,7 +120,36 @@
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+ override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ authenticationState.map { it.requestReason }
+
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ authenticationState
+ .filterIsInstance<AuthenticationState.AuthenticationAcquired>()
+ .filter {
+ it.biometricSourceType == BiometricSourceType.FINGERPRINT &&
+ // TODO(b/322555228) This check will be removed after consolidating device
+ // entry auth messages (currently in DeviceEntryFingerprintAuthRepository)
+ // with BP auth messages (here)
+ it.requestReason == AuthenticationReason.BiometricPromptAuthentication
+ }
+ .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+
companion object {
private const val TAG = "BiometricStatusRepositoryImpl"
}
}
+
+private fun Int.toAuthenticationReason(): AuthenticationReason =
+ when (this) {
+ REASON_AUTH_BP -> AuthenticationReason.BiometricPromptAuthentication
+ REASON_AUTH_KEYGUARD -> AuthenticationReason.DeviceEntryAuthentication
+ REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
+ REASON_AUTH_SETTINGS ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)
+ REASON_ENROLL_ENROLLING ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+ REASON_ENROLL_FIND_SENSOR ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR)
+ else -> AuthenticationReason.Unknown
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 55a2d3d..ed1557c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -20,6 +20,7 @@
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -31,6 +32,9 @@
* filtered for when the overlay should be shown, otherwise [NotRunning].
*/
val sfpsAuthenticationReason: Flow<AuthenticationReason>
+
+ /** The current status of an acquired fingerprint. */
+ val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
}
class BiometricStatusInteractorImpl
@@ -50,6 +54,9 @@
}
}
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ biometricStatusRepository.fingerprintAcquiredStatus
+
companion object {
private const val TAG = "BiometricStatusInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
new file mode 100644
index 0000000..77cf840
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.biometrics.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/**
+ * Describes the current state of biometric authentication, including whether authentication is
+ * started, stopped, or acquired and relevant parameters, and the [AuthenticationReason] for
+ * authentication.
+ */
+sealed interface AuthenticationState {
+ val requestReason: AuthenticationReason
+
+ /**
+ * Authentication started
+ *
+ * @param requestReason [AuthenticationReason] for starting authentication
+ */
+ data class AuthenticationStarted(override val requestReason: AuthenticationReason) :
+ AuthenticationState
+
+ /**
+ * Authentication stopped
+ *
+ * @param requestReason [AuthenticationReason.NotRunning]
+ */
+ data class AuthenticationStopped(override val requestReason: AuthenticationReason) :
+ AuthenticationState
+
+ /**
+ * Authentication acquired
+ *
+ * @param biometricSourceType indicates [BiometricSourceType] of acquired authentication
+ * @param requestReason indicates [AuthenticationReason] for requesting auth
+ * @param acquiredInfo indicates
+ */
+ data class AuthenticationAcquired(
+ val biometricSourceType: BiometricSourceType,
+ override val requestReason: AuthenticationReason,
+ val acquiredInfo: Int
+ ) : AuthenticationState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 285ab4a..efad21b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -41,6 +41,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -70,9 +71,9 @@
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
- view: BiometricPromptLayout,
+ view: View,
viewModel: PromptViewModel,
- panelViewController: AuthPanelController,
+ panelViewController: AuthPanelController?,
jankListener: BiometricJankListener,
backgroundView: View,
legacyCallback: Spaghetti.Callback,
@@ -112,11 +113,18 @@
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+ val iconSizeOverride =
+ if (constraintBp()) {
+ viewModel.fingerprintAffordanceSize
+ } else {
+ (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
+ }
+
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
- view.getUpdatedFingerprintAffordanceSize(),
- viewModel
+ iconSizeOverride,
+ viewModel,
)
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d5695f3..2417fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,29 +19,45 @@
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.graphics.Outline
+import android.graphics.Rect
+import android.transition.AutoTransition
+import android.transition.TransitionManager
import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.ViewOutlineProvider
import android.view.WindowInsets
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.Guideline
import androidx.core.animation.addListener
+import androidx.core.view.doOnAttach
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.biometrics.ui.viewmodel.isBottom
import com.android.systemui.biometrics.ui.viewmodel.isLarge
+import com.android.systemui.biometrics.ui.viewmodel.isLeft
import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
+import com.android.systemui.biometrics.ui.viewmodel.isRight
import com.android.systemui.biometrics.ui.viewmodel.isSmall
+import com.android.systemui.biometrics.ui.viewmodel.isTop
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.min
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -54,18 +70,19 @@
/** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
fun bind(
- view: BiometricPromptLayout,
+ view: View,
viewModel: PromptViewModel,
viewsToHideWhenSmall: List<View>,
viewsToFadeInOnSizeChange: List<View>,
- panelViewController: AuthPanelController,
+ panelViewController: AuthPanelController?,
jankListener: BiometricJankListener,
) {
val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
val accessibilityManager =
requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
+
fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+ Utils.notifyAccessibilityContentChanged(accessibilityManager, view as ViewGroup)
}
fun startMonitoredAnimation(animators: List<Animator>) {
@@ -77,149 +94,342 @@
}
}
- val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
- val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
- val fullSizeYOffset =
- view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
+ if (constraintBp()) {
+ val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+ val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
+ val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
- // cache the original position of the icon view (as done in legacy view)
- // this must happen before any size changes can be made
- view.doOnLayout {
- // TODO(b/251476085): this old way of positioning has proven itself unreliable
- // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
- // pin to the physical sensor
- val iconHolderOriginalY = iconHolderView.y
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
+ val panelView = view.requireViewById<View>(R.id.panel)
+ val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
- // bind to prompt
- // TODO(b/251476085): migrate the legacy panel controller and simplify this
- view.repeatWhenAttached {
- var currentSize: PromptSize? = null
- lifecycleScope.launch {
- /**
- * View is only set visible in BiometricViewSizeBinder once PromptSize is
- * determined that accounts for iconView size, to prevent prompt resizing being
- * visible to the user.
- *
- * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
- * layout is implemented
- */
- combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
- (isIconViewLoaded, size) ->
- if (!isIconViewLoaded) {
- return@collect
+ // ConstraintSets for animating between prompt sizes
+ val mediumConstraintSet = ConstraintSet()
+ mediumConstraintSet.clone(view as ConstraintLayout)
+
+ val smallConstraintSet = ConstraintSet()
+ smallConstraintSet.clone(mediumConstraintSet)
+ viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) }
+
+ val largeConstraintSet = ConstraintSet()
+ largeConstraintSet.clone(mediumConstraintSet)
+ viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) }
+ largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+ largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+ largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+ largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
+ largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
+ largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+
+ // Round the panel outline
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+ }
+ }
+
+ view.doOnLayout {
+ val windowBounds = windowManager.maximumWindowMetrics.bounds
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
+
+ fun measureBounds(position: PromptPosition) {
+ val width = min(windowBounds.height(), windowBounds.width())
+
+ var left = -1
+ var top = -1
+ var right = -1
+ var bottom = -1
+
+ when {
+ position.isTop -> {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top = viewModel.promptMargin
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
+ }
+ position.isBottom -> {
+ if (view.isLandscape()) {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top = iconHolderView.centerY()
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = bottomInset + viewModel.promptMargin
+ } else {
+ left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+ bottom = viewModel.promptMargin
+ }
}
- // prepare for animated size transitions
- for (v in viewsToHideWhenSmall) {
- v.showContentOrHide(forceHide = size.isSmall)
+ // For Udfps exclusive left and right, measure guideline to center
+ // icon in BP
+ position.isLeft -> {
+ left = viewModel.promptMargin
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right =
+ abs(
+ windowBounds.width() - iconHolderView.centerX() * 2 +
+ viewModel.promptMargin
+ )
+ bottom = bottomInset + viewModel.promptMargin
}
- if (currentSize == null && size.isSmall) {
- iconHolderView.alpha = 0f
+ position.isRight -> {
+ left =
+ abs(
+ iconHolderView.centerX() -
+ (windowBounds.width() - iconHolderView.centerX()) -
+ viewModel.promptMargin
+ )
+ top =
+ windowBounds.height() -
+ (windowBounds.height() - iconHolderView.centerY()) * 2 +
+ viewModel.promptMargin
+ right = viewModel.promptMargin
+ bottom = bottomInset + viewModel.promptMargin
}
- if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
- viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
- }
+ }
- // TODO(b/302735104): Fix wrong height due to the delay of
- // PromptContentView. addOnLayoutChangeListener() will cause crash when
- // showing credential view, since |PromptIconViewModel| won't release the
- // flow.
- // propagate size changes to legacy panel controller and animate transitions
- view.doOnLayout {
- val width = view.measuredWidth
- val height = view.measuredHeight
+ val bounds = Rect(left, top, right, bottom)
+ if (bounds.shouldAdjustLeftGuideline()) {
+ leftGuideline.setGuidelineBegin(bounds.left)
+ smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ }
+ if (bounds.shouldAdjustRightGuideline()) {
+ rightGuideline.setGuidelineEnd(bounds.right)
+ smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ }
+ if (bounds.shouldAdjustBottomGuideline()) {
+ bottomGuideline.setGuidelineEnd(bounds.bottom)
+ smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+ mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+ }
+ }
- when {
- size.isSmall -> {
- iconHolderView.alpha = 1f
- val bottomInset =
- windowManager.maximumWindowMetrics.windowInsets
- .getInsets(WindowInsets.Type.navigationBars())
- .bottom
- iconHolderView.y =
- if (view.isLandscape()) {
- (view.height - iconHolderView.height - bottomInset) / 2f
- } else {
- view.height -
- iconHolderView.height -
- iconPadding -
- bottomInset
- }
- val newHeight =
- iconHolderView.height + (2 * iconPadding.toInt()) -
- iconHolderView.paddingTop -
- iconHolderView.paddingBottom
- panelViewController.updateForContentDimensions(
- width,
- newHeight + bottomInset,
- 0, /* animateDurationMs */
- )
- }
- size.isMedium && currentSize.isSmall -> {
- val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
- panelViewController.updateForContentDimensions(
- width,
- height,
- duration,
- )
- startMonitoredAnimation(
- listOf(
- iconHolderView.asVerticalAnimator(
- duration = duration.toLong(),
- toY =
- iconHolderOriginalY -
- viewsToHideWhenSmall
- .filter { it.isGone }
- .sumOf { it.height },
- ),
- viewsToFadeInOnSizeChange.asFadeInAnimator(
- duration = duration.toLong(),
- delay = duration.toLong(),
- ),
+ view.repeatWhenAttached {
+ var currentSize: PromptSize? = null
+ lifecycleScope.launch {
+ combine(viewModel.position, viewModel.size, ::Pair).collect {
+ (position, size) ->
+ view.doOnAttach {
+ measureBounds(position)
+
+ when {
+ size.isSmall -> {
+ val ratio =
+ if (view.isLandscape()) {
+ (windowBounds.height() -
+ bottomInset -
+ viewModel.promptMargin)
+ .toFloat() / windowBounds.height()
+ } else {
+ (windowBounds.height() - viewModel.promptMargin)
+ .toFloat() / windowBounds.height()
+ }
+ smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
+
+ smallConstraintSet.applyTo(view as ConstraintLayout?)
+ }
+ size.isMedium && currentSize.isSmall -> {
+ val autoTransition = AutoTransition()
+ autoTransition.setDuration(
+ ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
)
- )
- }
- size.isMedium && currentSize.isNullOrNotSmall -> {
- panelViewController.updateForContentDimensions(
- width,
- height,
- 0, /* animateDurationMs */
- )
- }
- size.isLarge -> {
- val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
- panelViewController.setUseFullScreen(true)
- panelViewController.updateForContentDimensions(
- panelViewController.containerWidth,
- panelViewController.containerHeight,
- duration,
- )
- startMonitoredAnimation(
- listOf(
- view.asVerticalAnimator(
- duration.toLong() * 2 / 3,
- toY = view.y - fullSizeYOffset
- ),
- listOf(view)
- .asFadeInAnimator(
- duration = duration.toLong() / 2,
- delay = duration.toLong(),
- ),
+ TransitionManager.beginDelayedTransition(
+ view,
+ autoTransition
)
- )
- // TODO(b/251476085): clean up (copied from legacy)
- if (view.isAttachedToWindow) {
- val parent = view.parent as? ViewGroup
- parent?.removeView(view)
+ mediumConstraintSet.applyTo(view)
+ }
+ size.isLarge -> {
+ val autoTransition = AutoTransition()
+ autoTransition.setDuration(
+ ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+ )
+
+ TransitionManager.beginDelayedTransition(
+ view,
+ autoTransition
+ )
+ largeConstraintSet.applyTo(view)
}
}
+
+ currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
+ notifyAccessibilityChanged()
+
+ view.invalidate()
+ view.requestLayout()
+ }
+ }
+ }
+ }
+ }
+ } else if (panelViewController != null) {
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
+ val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
+ val fullSizeYOffset =
+ view.resources.getDimension(
+ R.dimen.biometric_dialog_medium_to_large_translation_offset
+ )
+
+ // cache the original position of the icon view (as done in legacy view)
+ // this must happen before any size changes can be made
+ view.doOnLayout {
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
+
+ // bind to prompt
+ // TODO(b/251476085): migrate the legacy panel controller and simplify this
+ view.repeatWhenAttached {
+ var currentSize: PromptSize? = null
+ lifecycleScope.launch {
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing
+ * being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
}
- currentSize = size
- view.visibility = View.VISIBLE
- viewModel.setIsIconViewLoaded(false)
- notifyAccessibilityChanged()
+ // prepare for animated size transitions
+ for (v in viewsToHideWhenSmall) {
+ v.showContentOrHide(forceHide = size.isSmall)
+ }
+ if (currentSize == null && size.isSmall) {
+ iconHolderView.alpha = 0f
+ }
+ if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
+ viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
+ }
+
+ // TODO(b/302735104): Fix wrong height due to the delay of
+ // PromptContentView. addOnLayoutChangeListener() will cause crash when
+ // showing credential view, since |PromptIconViewModel| won't release
+ // the
+ // flow.
+ // propagate size changes to legacy panel controller and animate
+ // transitions
+ view.doOnLayout {
+ val width = view.measuredWidth
+ val height = view.measuredHeight
+
+ when {
+ size.isSmall -> {
+ iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
+ iconHolderView.y =
+ if (view.isLandscape()) {
+ (view.height -
+ iconHolderView.height -
+ bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
+ val newHeight =
+ iconHolderView.height + (2 * iconPadding.toInt()) -
+ iconHolderView.paddingTop -
+ iconHolderView.paddingBottom
+ panelViewController.updateForContentDimensions(
+ width,
+ newHeight + bottomInset,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isMedium && currentSize.isSmall -> {
+ val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ duration,
+ )
+ startMonitoredAnimation(
+ listOf(
+ iconHolderView.asVerticalAnimator(
+ duration = duration.toLong(),
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
+ ),
+ viewsToFadeInOnSizeChange.asFadeInAnimator(
+ duration = duration.toLong(),
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ }
+ size.isMedium && currentSize.isNullOrNotSmall -> {
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isLarge -> {
+ val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+ panelViewController.setUseFullScreen(true)
+ panelViewController.updateForContentDimensions(
+ panelViewController.containerWidth,
+ panelViewController.containerHeight,
+ duration,
+ )
+
+ startMonitoredAnimation(
+ listOf(
+ view.asVerticalAnimator(
+ duration.toLong() * 2 / 3,
+ toY = view.y - fullSizeYOffset
+ ),
+ listOf(view)
+ .asFadeInAnimator(
+ duration = duration.toLong() / 2,
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ // TODO(b/251476085): clean up (copied from legacy)
+ if (view.isAttachedToWindow) {
+ val parent = view.parent as? ViewGroup
+ parent?.removeView(view)
+ }
+ }
+ }
+
+ currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
+ notifyAccessibilityChanged()
+ }
}
}
}
@@ -244,6 +454,20 @@
}
}
+private fun View.centerX(): Int {
+ return (x + width / 2).toInt()
+}
+
+private fun View.centerY(): Int {
+ return (y + height / 2).toInt()
+}
+
+private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
+
+private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
+
+private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
+
private fun View.asVerticalAnimator(
duration: Long,
toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 6e3bcf5..2e47375 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,13 +17,17 @@
package com.android.systemui.biometrics.ui.binder
+import android.graphics.Rect
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
@@ -119,6 +123,24 @@
}
launch {
+ viewModel.iconPosition.collect { position ->
+ if (constraintBp() && position != Rect()) {
+ val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
+
+ if (position.left != -1) {
+ iconParams.endToEnd = ConstraintSet.UNSET
+ iconParams.leftMargin = position.left
+ }
+ if (position.top != -1) {
+ iconParams.bottomToBottom = ConstraintSet.UNSET
+ iconParams.topMargin = position.top
+ }
+ iconView.layoutParams = iconParams
+ }
+ }
+ }
+
+ launch {
viewModel.iconAsset
.sample(
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 80d37b4..7b4be02 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -50,10 +50,12 @@
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class SideFpsOverlayViewBinder
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b..2e29c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics.ui.binder
+import android.util.Log
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@
/**
* Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
- * touches or not.
+ * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+ * viewBinder will send the information to the interactor.
*/
@JvmStatic
fun bind(
view: UdfpsTouchOverlay,
viewModel: UdfpsTouchOverlayViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ Log.d(
+ "UdfpsTouchOverlayBinder",
+ "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ )
view.isInvisible = !shouldHandleTouches
+ udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 3defec5..b7cffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -20,8 +20,11 @@
import android.annotation.DrawableRes
import android.annotation.RawRes
import android.content.res.Configuration
+import android.graphics.Rect
+import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.res.R
@@ -42,7 +45,8 @@
constructor(
promptViewModel: PromptViewModel,
private val displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor
+ promptSelectorInteractor: PromptSelectorInteractor,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
/** Auth types for the UI to display. */
@@ -71,7 +75,40 @@
} else if (modalities.hasFingerprintOnly) {
AuthType.Fingerprint
} else {
- throw IllegalStateException("unexpected modality: $modalities")
+ // TODO(b/288175072): Remove, currently needed for transition to credential view
+ AuthType.Fingerprint
+ }
+ }
+
+ val udfpsSensorBounds: Flow<Rect> =
+ combine(
+ udfpsOverlayInteractor.udfpsOverlayParams,
+ displayStateInteractor.currentRotation
+ ) { params, rotation ->
+ val rotatedBounds = Rect(params.sensorBounds)
+ RotationUtils.rotateBounds(
+ rotatedBounds,
+ params.naturalDisplayWidth,
+ params.naturalDisplayHeight,
+ rotation.ordinal
+ )
+ rotatedBounds
+ }
+ .distinctUntilChanged()
+
+ val iconPosition: Flow<Rect> =
+ combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
+ sensorBounds,
+ size,
+ modalities ->
+ // If not Udfps, icon does not change from default layout position
+ if (!modalities.hasUdfps) {
+ Rect() // Empty rect, don't offset from default position
+ } else if (size.isSmall) {
+ // When small with Udfps, only set horizontal position
+ Rect(sensorBounds.left, -1, sensorBounds.right, -1)
+ } else {
+ sensorBounds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
new file mode 100644
index 0000000..d45dad6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.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.biometrics.ui.viewmodel
+
+/** The position of a biometric prompt */
+enum class PromptPosition {
+ Top,
+ Bottom,
+ Left,
+ Right,
+}
+
+val PromptPosition?.isBottom: Boolean
+ get() = this != null && this == PromptPosition.Bottom
+
+val PromptPosition?.isLeft: Boolean
+ get() = this != null && this == PromptPosition.Left
+
+val PromptPosition?.isRight: Boolean
+ get() = this != null && this == PromptPosition.Right
+
+val PromptPosition?.isTop: Boolean
+ get() = this != null && this == PromptPosition.Top
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0f1340a..ef5c37ea 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
@@ -81,11 +82,23 @@
val faceIconHeight: Int =
context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ val fingerprintSensorDiameter: Int =
+ (udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width() *
+ udfpsOverlayInteractor.udfpsOverlayParams.value.scaleFactor)
+ .toInt()
+ val fingerprintAffordanceSize: Pair<Int, Int>? =
+ if (fingerprintSensorDiameter != 0)
+ Pair(fingerprintSensorDiameter, fingerprintSensorDiameter)
+ else null
+
private val _accessibilityHint = MutableSharedFlow<String>()
/** Hint for talkback directional guidance */
val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
+ val promptMargin: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -135,6 +148,22 @@
/** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
val hapticsToPlay = _hapticsToPlay.asStateFlow()
+ /** The current position of the prompt */
+ val position: Flow<PromptPosition> =
+ combine(_forceLargeSize, modalities, displayStateInteractor.currentRotation) {
+ forceLarge,
+ modalities,
+ rotation ->
+ when {
+ forceLarge || !modalities.hasUdfps -> PromptPosition.Bottom
+ rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
+ rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
+ rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
+ else -> PromptPosition.Bottom
+ }
+ }
+ .distinctUntilChanged()
+
/** The size of the prompt. */
val size: Flow<PromptSize> =
combine(
@@ -195,7 +224,12 @@
.distinctUntilChanged()
val iconViewModel: PromptIconViewModel =
- PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ PromptIconViewModel(
+ this,
+ displayStateInteractor,
+ promptSelectorInteractor,
+ udfpsOverlayInteractor
+ )
private val _isIconViewLoaded = MutableStateFlow(false)
@@ -244,7 +278,13 @@
!customBiometricPrompt() || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ else ->
+ try {
+ context.packageManager.getApplicationIcon(it.opPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
+ null
+ }
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index ce72603..cfda75c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -41,12 +41,14 @@
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
/** Models UI of the side fingerprint sensor indicator view. */
+@OptIn(ExperimentalCoroutinesApi::class)
class SideFpsOverlayViewModel
@Inject
constructor(
@@ -176,8 +178,8 @@
val lottieCallbacks: Flow<List<LottieCallback>> =
combine(
biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
- sideFpsProgressBarViewModel.isVisible,
+ deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.isVisible
) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
->
val callbacks = mutableListOf<LottieCallback>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index c46f0d1..33edb80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -21,21 +21,21 @@
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
- val hasAnyMediaOrRecommendation: Boolean,
+ val hasActiveMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = false,
+ hasActiveMediaOrRecommendation = false,
)
}
override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
- if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+ if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
row.logChange(
columnName = "isMediaActive",
- value = hasAnyMediaOrRecommendation,
+ value = hasActiveMediaOrRecommendation,
)
}
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 2b66491..201be51 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
@@ -73,10 +73,10 @@
)
private fun updateMediaModel(data: MediaData? = null) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ if (mediaDataManager.hasActiveMediaOrRecommendation()) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 85aeb4d..0e9b32f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -28,8 +28,8 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -97,8 +97,8 @@
}
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
- userFileManager
- .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ getSharedPrefsForUser(user)
+ .observe(CTA_DISMISSED_STATE)
// Emit at the start of collection to ensure we get an initial value
.onStart { emit(Unit) }
.map { getCtaDismissedState() }
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 75a27a2..950ac3c 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
@@ -318,7 +318,7 @@
)
// Add UMO
- if (media.hasAnyMediaOrRecommendation) {
+ if (media.hasActiveMediaOrRecommendation) {
ongoingContent.add(
CommunalContentModel.Umo(
createdTimestampMillis = media.createdTimestampMillis,
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 0c12841..40d2d16 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
@@ -87,7 +87,7 @@
with(mediaHost) {
expansion = MediaHostState.EXPANDED
expandedMatchesParentHeight = true
- showsOnlyActiveMedia = false
+ showsOnlyActiveMedia = true
falsingProtectionNeeded = false
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index ae9c37a..b35bec4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -17,11 +17,16 @@
package com.android.systemui.controls.panels
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+
/**
* Repository for keeping track of which packages the panel has authorized to show control panels
* (embedded activity).
*/
interface AuthorizedPanelsRepository {
+ /** Exposes the authorized panels as a [Flow] for subscribing to updates */
+ fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>>
/** A set of package names that the user has previously authorized to show panels. */
fun getAuthorizedPanels(): Set<String>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 4e935df..7c2dae3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -19,11 +19,16 @@
import android.content.Context
import android.content.SharedPreferences
+import android.os.UserHandle
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
class AuthorizedPanelsRepositoryImpl
@Inject
@@ -33,19 +38,24 @@
private val userTracker: UserTracker,
) : AuthorizedPanelsRepository {
+ override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
+ val prefs = instantiateSharedPrefs(user)
+ return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+ }
+
override fun getAuthorizedPanels(): Set<String> {
- return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+ return getAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle))
}
override fun getPreferredPackages(): Set<String> =
context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
override fun addAuthorizedPanels(packageNames: Set<String>) {
- addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+ addAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle), packageNames)
}
override fun removeAuthorizedPanels(packageNames: Set<String>) {
- with(instantiateSharedPrefs()) {
+ with(instantiateSharedPrefs(userTracker.userHandle)) {
val currentSet = getAuthorizedPanelsInternal(this)
edit().putStringSet(KEY, currentSet - packageNames).apply()
}
@@ -63,12 +73,12 @@
sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
}
- private fun instantiateSharedPrefs(): SharedPreferences {
+ private fun instantiateSharedPrefs(user: UserHandle): SharedPreferences {
val sharedPref =
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
Context.MODE_PRIVATE,
- userTracker.userId,
+ user.identifier,
)
// We should add default packages when we've never run this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0baa81a..9be04940 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -20,21 +20,18 @@
import android.content.Context
import android.content.SharedPreferences
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -43,9 +40,7 @@
constructor(
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
- private val featureFlags: FeatureFlags,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val applicationScope: CoroutineScope
+ @Background private val bgDispatcher: CoroutineDispatcher
) : SelectedComponentRepository {
private companion object {
@@ -66,22 +61,11 @@
override fun selectedComponentFlow(
userHandle: UserHandle
): Flow<SelectedComponentRepository.SelectedComponent?> {
- return conflatedCallbackFlow {
- val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
- applicationScope.launch(bgDispatcher) {
- if (key == PREF_COMPONENT) {
- trySend(getSelectedComponent(userHandle))
- }
- }
- }
- sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
- send(getSelectedComponent(userHandle))
- awaitClose {
- sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
- }
- }
+ val prefs = getSharedPreferencesForUser(userHandle.identifier)
+ return prefs
+ .observe(PREF_COMPONENT)
+ .onStart { emit(Unit) }
+ .map { getSelectedComponent(userHandle) }
.flowOn(bgDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94..dd186d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@
import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045..a3d6ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 91e0547..0cab10db 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -47,24 +47,30 @@
@Inject
constructor(
private val selectedComponentRepository: SelectedComponentRepository,
- private val controlsComponent: ControlsComponent,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ controlsComponent: ControlsComponent,
+ authorizedPanelsRepository: AuthorizedPanelsRepository,
userRepository: UserRepository,
@Background private val bgScope: CoroutineScope
) {
- private val controlsListingController =
+ private val controlsListingController: ControlsListingController? =
controlsComponent.getControlsListingController().getOrNull()
/** Gets the current user's selected panel, or null if there isn't one */
- private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+ private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
userRepository.selectedUserInfo
.flatMapLatest { user ->
selectedComponentRepository.selectedComponentFlow(user.userHandle)
}
.map { if (it?.isPanel == true) it else null }
- /** Gets all the available panels which are authorized by the user */
- private fun allPanelItem(): Flow<List<PanelComponent>> {
+ /** Gets the current user's authorized panels */
+ private val allAuthorizedPanels: Flow<Set<String>> =
+ userRepository.selectedUserInfo.flatMapLatest { user ->
+ authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+ }
+
+ /** Gets all the available services from [ControlsListingController] */
+ private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
if (controlsListingController == null) {
return emptyFlow()
}
@@ -79,26 +85,38 @@
awaitClose { controlsListingController.removeCallback(listener) }
}
.onStart { emit(controlsListingController.getCurrentServices()) }
- .map { serviceInfos ->
- val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
- serviceInfos.mapNotNull {
- val panelActivity = it.panelActivity
- if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
- PanelComponent(it.componentName, panelActivity)
- } else {
- null
- }
+ }
+
+ /** Gets all panels which are available and authorized by the user */
+ private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+ combine(
+ allAvailableServices(),
+ allAuthorizedPanels,
+ ) { serviceInfos, authorizedPanels ->
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
}
}
- }
+ }
+
val panelComponent: StateFlow<ComponentName?> =
- combine(allPanelItem(), selectedItem) { items, selected ->
+ combine(
+ allAvailableAndAuthorizedPanels,
+ selectedPanel,
+ ) { panels, selected ->
val item =
- items.firstOrNull { it.componentName == selected?.componentName }
- ?: items.firstOrNull()
+ panels.firstOrNull { it.componentName == selected?.componentName }
+ ?: panels.firstOrNull()
item?.panelActivity
}
.stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
- data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+ private data class PanelComponent(
+ val componentName: ComponentName,
+ val panelActivity: ComponentName,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index df0566e..41ce3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,9 +23,12 @@
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -55,6 +58,11 @@
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+
+ // ComposeLockscreen dependencies
+ ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token
+ ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
+ ComposeLockscreen.token dependsOn migrateClocksToBlueprint
}
private inline val politeNotifications
@@ -65,4 +73,6 @@
get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
private inline val keyguardBottomAreaRefactor
get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+ private inline val migrateClocksToBlueprint
+ get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index d5f082a..72a81cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -19,10 +19,10 @@
@JvmInline
value class Locked(val locked: Boolean)
-enum class ModifierKey(val text: String) {
+enum class ModifierKey(val displayedText: String) {
ALT("ALT LEFT"),
ALT_GR("ALT RIGHT"),
CTRL("CTRL"),
- META("META"),
+ META("ACTION"),
SHIFT("SHIFT"),
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b5f9c69..4cabd70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -108,20 +106,7 @@
private final ScreenOnCoordinator mScreenOnCoordinator;
private final ShellTransitions mShellTransitions;
private final DisplayTracker mDisplayTracker;
- private PowerInteractor mPowerInteractor;
-
- private static int newModeToLegacyMode(int newMode) {
- switch (newMode) {
- case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
- return MODE_OPENING;
- case WindowManager.TRANSIT_CLOSE:
- case WindowManager.TRANSIT_TO_BACK:
- return MODE_CLOSING;
- default:
- return 2; // MODE_CHANGING
- }
- }
+ private final PowerInteractor mPowerInteractor;
private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@
public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
SurfaceControl.Transaction candidateT, IBinder currentTransition,
- IRemoteTransitionFinishedCallback candidateFinishCallback)
- throws RemoteException {
+ IRemoteTransitionFinishedCallback candidateFinishCallback) {
if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
keyguardViewMediator.setPendingLock(true);
keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@
runner.onAnimationCancelled();
finish(currentTransition);
} catch (RemoteException e) {
- // nothing, we'll just let it finish on its own I guess.
+ // Ignore.
}
}
@Override
- public void onTransitionConsumed(IBinder transition, boolean aborted)
- throws RemoteException {
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {
+ // No-op.
}
private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@
}
private void finish(IBinder transition) throws RemoteException {
- IRemoteTransitionFinishedCallback finishCallback = null;
+ final IRemoteTransitionFinishedCallback finishCallback;
SurfaceControl.Transaction finishTransaction = null;
synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa..f085e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -326,7 +327,6 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
- private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
- private SystemPropertiesHelper mSystemPropertiesHelper;
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
* Used to keep the device awake while to ensure the keyguard finishes opening before
* we sleep.
*/
- private PowerManager.WakeLock mShowKeyguardWakeLock;
+ private final PowerManager.WakeLock mShowKeyguardWakeLock;
private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
@@ -405,13 +405,13 @@
private boolean mWakeAndUnlocking = false;
/**
- * Helps remember whether the screen has turned on since the last time
- * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ * Helps remember whether the screen has turned on since the last time it turned off due to
+ * timeout. See {@link #onScreenTurnedOff}
*/
private int mDelayedShowingSequence;
/**
- * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -439,7 +439,7 @@
private boolean mGoingToSleep;
// last known state of the cellular connection
- private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+ private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
@@ -1088,8 +1088,7 @@
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- if (!handleOnAnimationStart(
- transit, apps, wallpapers, nonApps, finishedCallback)) {
+ if (!handleOnAnimationStart(apps, finishedCallback)) {
// Usually we rely on animation completion to synchronize occluded status,
// but there was no animation to play, so just update it now.
setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1096,8 @@
}
}
- private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
@@ -1107,8 +1105,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo != null
- && apps[0].taskInfo.topActivityType
+ final boolean isDream = (primary.taskInfo != null
+ && primary.taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1320,9 @@
}
private DeviceConfigProxy mDeviceConfig;
- private DozeParameters mDozeParameters;
- private SelectedUserInteractor mSelectedUserInteractor;
- private KeyguardInteractor mKeyguardInteractor;
+ private final DozeParameters mDozeParameters;
+ private final SelectedUserInteractor mSelectedUserInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1346,14 +1344,12 @@
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
- private ScreenOnCoordinator mScreenOnCoordinator;
private final KeyguardTransitions mKeyguardTransitions;
- private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
- private Lazy<ScrimController> mScrimControllerLazy;
- private IActivityTaskManager mActivityTaskManagerService;
+ private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private final Lazy<ScrimController> mScrimControllerLazy;
+ private final IActivityTaskManager mActivityTaskManagerService;
- private FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1357,7 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1429,6 @@
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
mDeviceConfig = deviceConfig;
- mScreenOnCoordinator = screenOnCoordinator;
mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1440,8 @@
mHandler::post,
mOnPropertiesChangedListener);
mInGestureNavigationMode =
- QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
- mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
- }));
+ QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+ mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1468,6 @@
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
@@ -1578,14 +1571,6 @@
this::setWallpaperSupportsAmbientMode);
}
- // TODO(b/273443374) remove, temporary util to get a feature flag
- private WallpaperManager getWallpaperManager() {
- if (mWallpaperManager == null) {
- mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
- }
- return mWallpaperManager;
- }
-
@Override
public void start() {
synchronized (this) {
@@ -1611,11 +1596,11 @@
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+ DreamingToLockscreenTransitionViewModel viewModel =
+ mDreamingToLockscreenTransitionViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+ collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
}
}
@@ -2304,6 +2289,7 @@
showKeyguard(options);
}
+ @SuppressLint("MissingPermission")
private void lockProfile(int userId) {
mTrustManager.setDeviceLockedForUser(userId, true);
}
@@ -2497,13 +2483,12 @@
};
/**
- * This handler will be associated with the policy thread, which will also
- * be the UI thread of the keyguard. Since the apis of the policy, and therefore
- * this class, can be called by other threads, any action that directly
- * interacts with the keyguard ui should be posted to this handler, rather
- * than called directly.
+ * This handler will be associated with the policy thread, which will also be the UI thread of
+ * the keyguard. Since the apis of the policy, and therefore this class, can be called by other
+ * threads, any action that directly interacts with the keyguard ui should be posted to this
+ * handler, rather than called directly.
*/
- private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
String message = "";
@@ -2766,7 +2751,7 @@
try {
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
- } catch (RemoteException e) {
+ } catch (RemoteException ignored) {
}
});
}
@@ -2790,9 +2775,8 @@
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
return;
- } else {
- if (DEBUG) Log.d(TAG, "handleShow");
}
+ if (DEBUG) Log.d(TAG, "handleShow");
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
@@ -2851,6 +2835,7 @@
}
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ @SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2910,13 @@
return;
}
- final String reasonDescription;
-
- switch(reason) {
- case WakeAndUnlockUpdateReason.FULFILL:
- reasonDescription = "fulfilling existing request";
- break;
- case WakeAndUnlockUpdateReason.HIDE:
- reasonDescription = "hiding keyguard";
- break;
- case WakeAndUnlockUpdateReason.SHOW:
- reasonDescription = "showing keyguard";
- break;
- case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
- reasonDescription = "waking to unlock";
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + reason);
- }
+ final String reasonDescription = switch (reason) {
+ case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+ case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+ case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+ case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+ default -> throw new IllegalStateException("Unexpected value: " + reason);
+ };
final boolean unsetUnfulfilled = !updatedValue
&& reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3031,7 @@
IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback() {
@Override
- public void onAnimationFinished() throws RemoteException {
+ public void onAnimationFinished() {
if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
@@ -3542,11 +3516,6 @@
/**
* Registers the CentralSurfaces to which the Keyguard View is mounted.
*
- * @param centralSurfaces
- * @param panelView
- * @param biometricUnlockController
- * @param notificationContainer
- * @param bypassController
* @return the View Controller for the Keyguard View this class is mediating.
*/
public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3742,7 @@
}
});
updateInputRestrictedLocked();
- mUiBgExecutor.execute(() -> {
- mTrustManager.reportKeyguardShowingChanged();
- });
+ mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
private void notifyTrustedChangedLocked(boolean trusted) {
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 e16f8dc..70da3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@
import java.util.concurrent.Executor;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Dagger Module providing keyguard.
*/
+@ExperimentalCoroutinesApi
@Module(subcomponents = {
KeyguardQsUserSwitchComponent.class,
KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@
*/
@Provides
@SysUISingleton
- public static KeyguardViewMediator newKeyguardViewMediator(
+ static KeyguardViewMediator newKeyguardViewMediator(
Context context,
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 9a13558d..b152eea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -22,6 +22,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -174,6 +175,8 @@
mainDispatcher
) // keyguardUpdateMonitor requires registration on main thread.
+ // TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
+ // in BiometricStatusRepository
override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
get() = conflatedCallbackFlow {
val callback =
@@ -236,7 +239,8 @@
sendUpdateIfFingerprint(
biometricSourceType,
AcquiredFingerprintAuthenticationStatus(
- acquireInfo,
+ AuthenticationReason.DeviceEntryAuthentication,
+ acquireInfo
),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index b1a2297..e017129 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -94,6 +94,7 @@
context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+
return primaryBouncerInteractor.isBouncerShowing() &&
sfpsEnabled &&
sfpsDetectionRunning &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
new file mode 100644
index 0000000..7f0b483
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
@@ -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.systemui.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the compose lockscreen flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ComposeLockscreen {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index cc385a8..474de77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -20,6 +20,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
/**
* Fingerprint authentication status provided by
@@ -40,8 +41,10 @@
) : FingerprintAuthenticationStatus()
/** Fingerprint acquired message. */
-data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
- FingerprintAuthenticationStatus() {
+data class AcquiredFingerprintAuthenticationStatus(
+ val authenticationReason: AuthenticationReason,
+ val acquiredInfo: Int
+) : FingerprintAuthenticationStatus() {
val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6..789d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@
.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
}
}
@@ -200,6 +201,7 @@
.collect { x ->
childViews[burnInLayerId]?.translationX = x
childViews[largeClockId]?.translationX = x
+ childViews[aodNotificationIconContainerId]?.translationX = x
}
}
@@ -219,6 +221,10 @@
// transition with other parts in burnInLayer
childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 3d36eb0..9a1fcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -41,11 +41,13 @@
return
}
- val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+ // The burn-in layer requires at least 1 view at all times
+ val emptyView = View(context, null).apply { id = View.generateViewId() }
+ constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
- addView(nic)
+ addView(emptyView)
if (!migrateClocksToBlueprint()) {
val statusView =
constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index ca9c857..67c42f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -22,8 +22,10 @@
import androidx.annotation.VisibleForTesting
import androidx.core.animation.addListener
import com.android.systemui.Flags
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -49,10 +52,12 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
@@ -62,7 +67,8 @@
@Inject
constructor(
private val context: Context,
- private val fpAuthRepository: DeviceEntryFingerprintAuthInteractor,
+ private val biometricStatusInteractor: BiometricStatusInteractor,
+ private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val sfpsSensorInteractor: SideFpsSensorInteractor,
// todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
// DozeInteractor as DozeServiceHost already depends on DozeInteractor.
@@ -86,6 +92,23 @@
private val additionalSensorLengthPadding =
context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
+ // Merged [FingerprintAuthenticationStatus] from BiometricPrompt acquired messages and
+ // device entry authentication messages
+ private val mergedFingerprintAuthenticationStatus =
+ merge(
+ biometricStatusInteractor.fingerprintAcquiredStatus,
+ deviceEntryFingerprintAuthInteractor.authenticationStatus
+ )
+ .filter {
+ if (it is AcquiredFingerprintAuthenticationStatus) {
+ it.authenticationReason == AuthenticationReason.DeviceEntryAuthentication ||
+ it.authenticationReason ==
+ AuthenticationReason.BiometricPromptAuthentication
+ } else {
+ true
+ }
+ }
+
val isVisible: Flow<Boolean> = _visible.asStateFlow()
val progress: Flow<Float> = _progress.asStateFlow()
@@ -147,7 +170,14 @@
viewLeftTop
}
- val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+ val isFingerprintAuthRunning: Flow<Boolean> =
+ combine(
+ deviceEntryFingerprintAuthInteractor.isRunning,
+ biometricStatusInteractor.sfpsAuthenticationReason
+ ) { deviceEntryAuthIsRunning, sfpsAuthReason ->
+ deviceEntryAuthIsRunning ||
+ sfpsAuthReason == AuthenticationReason.BiometricPromptAuthentication
+ }
val rotation: Flow<Float> =
combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
@@ -185,7 +215,8 @@
sfpsSensorInteractor.authenticationDuration
.flatMapLatest { authDuration ->
_animator?.cancel()
- fpAuthRepository.authenticationStatus.map { authStatus ->
+ mergedFingerprintAuthenticationStatus.map {
+ authStatus: FingerprintAuthenticationStatus ->
when (authStatus) {
is AcquiredFingerprintAuthenticationStatus -> {
if (authStatus.fingerprintCaptureStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 23029e6..ac579d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -628,4 +628,13 @@
public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
return factory.create("PackageChangeRepo", 50);
}
+
+ /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+ @Provides
+ @SysUISingleton
+ @NavBarButtonClickLog
+ public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+ return factory.create("NavBarButtonClick", 50);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 0000000..939dab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 0000000..408acf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+ fun logHomeButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+ }
+
+ fun logBackButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+ }
+
+ fun logRecentsButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+ }
+
+ fun logImeSwitcherClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+ }
+
+ fun logAccessibilityButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+ }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd..95b75ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@
private boolean mImeVisible;
private final Rect mSamplingBounds = new Rect();
private final Binder mInsetsSourceOwner = new Binder();
+ private final NavBarButtonClickLogger mNavBarButtonClickLogger;
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@
UserContextProvider userContextProvider,
WakefulnessLifecycle wakefulnessLifecycle,
TaskStackChangeListeners taskStackChangeListeners,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ NavBarButtonClickLogger navBarButtonClickLogger) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -601,6 +599,7 @@
mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayTracker = displayTracker;
mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@
ButtonDispatcher homeButton = mView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
+ homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+ ButtonDispatcher backButton = mView.getBackButton();
+ backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
reconfigureHomeLongClick();
@@ -1388,6 +1391,8 @@
}
private void onRecentsClick(View v) {
+ mNavBarButtonClickLogger.logRecentsButtonClick();
+
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@
}
private void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@
}
private void onAccessibilityClick(View v) {
+ mNavBarButtonClickLogger.logAccessibilityButtonClick();
final Display display = v.getDisplay();
mAccessibilityManager.notifyAccessibilityButtonClicked(
display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abc..fc37b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@
import android.animation.ValueAnimator;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import java.util.ArrayList;
@@ -52,6 +55,7 @@
private boolean mVertical;
private ValueAnimator mFadeAnimator;
private AccessibilityDelegate mAccessibilityDelegate;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
setAlpha(
@@ -341,4 +345,36 @@
*/
public void onDestroy() {
}
+
+ /**
+ * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+ */
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ if (navBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ final int size = mViews.size();
+ for (int i = 0; i < size; i++) {
+ final View v = mViews.get(i);
+ setNavBarButtonClickLoggerForViewChildren(v);
+ }
+ }
+ }
+
+ /**
+ * Recursively explores view hierarchy until the children of provided view are of type
+ * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+ */
+ private void setNavBarButtonClickLoggerForViewChildren(View v) {
+ if (v instanceof KeyButtonView) {
+ ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+ return;
+ }
+
+ if (v instanceof ViewGroup viewGroup) {
+ final int childrenCount = viewGroup.getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d..dbe87ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
@VisibleForTesting
public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@
mOnClickListener = onClickListener;
}
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ }
+
public void loadAsync(Icon icon) {
new AsyncTask<Icon, Void, Drawable>() {
@Override
@@ -389,11 +395,19 @@
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logBackButtonClick();
+ }
break;
case KeyEvent.KEYCODE_HOME:
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logHomeButtonClick();
+ }
break;
case KeyEvent.KEYCODE_APP_SWITCH:
uiEvent = longPressSet
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 37abc40..56c0ca9 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
@@ -52,6 +52,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
@@ -115,11 +116,15 @@
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- deviceProvisioningInteractor.isFactoryResetProtectionActive
- .flatMapLatest { isFrpActive ->
- if (isFrpActive) {
- flowOf(false to "Factory Reset Protection is active")
- } else {
+ combine(
+ deviceProvisioningInteractor.isDeviceProvisioned,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
+ ) { isDeviceProvisioned, isFrpActive ->
+ isDeviceProvisioned && !isFrpActive
+ }
+ .distinctUntilChanged()
+ .flatMapLatest { isAllowedToBeVisible ->
+ if (isAllowedToBeVisible) {
sceneInteractor.transitionState
.mapNotNull { state ->
when (state) {
@@ -140,6 +145,8 @@
}
}
.distinctUntilChanged()
+ } else {
+ flowOf(false to "Device not provisioned or Factory Reset Protection active")
}
}
.collect { (isVisible, loggingReason) ->
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
rename to packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924..fd807db 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -29,14 +29,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
import dagger.Binds;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 0000000..76d1d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SecureSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSecureSettingsRepository(
+ contentResolver: ContentResolver,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ ): SecureSettingsRepository =
+ SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
deleted file mode 100644
index b09bfe2..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
+++ /dev/null
@@ -1,43 +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.settings
-
-import android.annotation.UserIdInt
-import android.content.Context
-import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Extension functions for [UserFileManager]. */
-object UserFileManagerExt {
-
- /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
- fun UserFileManager.observeSharedPreferences(
- fileName: String,
- @Context.PreferencesMode mode: Int,
- @UserIdInt userId: Int
- ): Flow<Unit> = conflatedCallbackFlow {
- val sharedPrefs = getSharedPreferences(fileName, mode, userId)
-
- val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index a01ac70..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -183,7 +183,8 @@
mBackgroundExecutor = backgroundExecutor;
mColorExtractor = colorExtractor;
mScreenOffAnimationController = screenOffAnimationController;
- dumpManager.registerDumpable(this);
+ // prefix with {slow} to make sure this dumps at the END of the critical section.
+ dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
mUserInteractor = userInteractor;
mSceneContainerFlags = sceneContainerFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 10b9db0..4e8b403 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.assist.AssistManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
@@ -34,11 +35,13 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Implementation of ShadeController backed by scenes instead of NPVC.
@@ -50,6 +53,7 @@
class ShadeControllerSceneImpl
@Inject
constructor(
+ @Main private val mainDispatcher: CoroutineDispatcher,
@Background private val scope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
@@ -193,7 +197,11 @@
}
override fun setVisibilityListener(listener: ShadeVisibilityListener) {
- scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+ scope.launch {
+ sceneInteractor.isVisible.collect { isVisible ->
+ withContext(mainDispatcher) { listener.expandedVisibleChanged(isVisible) }
+ }
+ }
}
@ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f8be1c..ef50265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,6 +14,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.ExpandHelper
+import com.android.systemui.Flags.nsslFalsingFix
import com.android.systemui.Gefingerpoken
import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
import com.android.systemui.classifier.Classifier
@@ -889,7 +890,7 @@
isDraggingDown = false
isTrackpadReverseScroll = false
shadeRepository.setLegacyLockscreenShadeTracking(false)
- if (KeyguardShadeMigrationNssl.isEnabled) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) {
return true
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 32cd56c..b64e0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -73,7 +72,7 @@
* their own version of CentralSurfaces can include just dependencies, without injecting
* CentralSurfaces itself.
*/
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
public interface CentralSurfacesDependenciesModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e..27536bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import dagger.Module;
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/** */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+ StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
NotificationsModule.class, NotificationRowModule.class})
public interface CentralSurfacesModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27..29627e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816ae..954e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
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 6bba72b..92b0c04 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
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@@ -94,6 +95,7 @@
FooterViewModelModule.class,
KeyguardNotificationVisibilityProviderModule.class,
NotificationDataLayerModule.class,
+ NotificationDomainLayerModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
ActivatableNotificationViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2cac000..b187cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -17,4 +17,10 @@
import dagger.Module
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+ includes =
+ [
+ NotificationSettingsRepositoryModule::class,
+ ]
+)
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
new file mode 100644
index 0000000..a7970c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.notification.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsRepository(
+ @Background backgroundScope: CoroutineScope,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ secureSettingsRepository: SecureSettingsRepository,
+ ): NotificationSettingsRepository =
+ NotificationSettingsRepository(
+ backgroundScope,
+ backgroundDispatcher,
+ secureSettingsRepository
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
new file mode 100644
index 0000000..5c49b28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.notification.domain
+
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 0000000..0a9e12a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+ NotificationSettingsInteractor(repository)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3616fd6d..16f18a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -54,7 +54,7 @@
private static final String TAG = "FooterView";
private FooterViewButton mClearAllButton;
- private FooterViewButton mManageButton;
+ private FooterViewButton mManageOrHistoryButton;
private boolean mShowHistory;
// String cache, for performance reasons.
// Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@
private @StringRes int mClearAllButtonTextId;
private @StringRes int mClearAllButtonDescriptionId;
+ private @StringRes int mManageOrHistoryButtonTextId;
+ private @StringRes int mManageOrHistoryButtonDescriptionId;
private @StringRes int mMessageStringId;
private @DrawableRes int mMessageIconId;
@@ -155,6 +157,43 @@
mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
}
+ /** Set the text label for the "Manage"/"History" button. */
+ public void setManageOrHistoryButtonText(@StringRes int textId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mManageOrHistoryButtonTextId == textId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonTextId = textId;
+ updateManageOrHistoryButtonText();
+ }
+
+ private void updateManageOrHistoryButtonText() {
+ if (mManageOrHistoryButtonTextId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+ }
+
+ /** Set the accessibility content description for the "Clear all" button. */
+ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+ updateManageOrHistoryButtonDescription();
+ }
+
+ private void updateManageOrHistoryButtonDescription() {
+ if (mManageOrHistoryButtonDescriptionId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setContentDescription(
+ getContext().getString(mManageOrHistoryButtonDescriptionId));
+ }
+
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@
mSeenNotifsFooterTextView.setText(messageString);
}
-
/** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
public void setMessageIcon(@DrawableRes int iconId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
- mManageButton = findViewById(R.id.manage_text);
+ mManageOrHistoryButton = findViewById(R.id.manage_text);
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
updateColors();
}
@@ -213,11 +253,11 @@
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
if (isVisible) {
- mManageButton.setVisibility(View.GONE);
+ mManageOrHistoryButton.setVisibility(View.GONE);
mClearAllButton.setVisibility(View.GONE);
mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
- mManageButton.setVisibility(View.VISIBLE);
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
mClearAllButton.setVisibility(View.VISIBLE);
mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
@@ -225,7 +265,7 @@
/** Set onClickListener for the manage/history button. */
public void setManageButtonClickListener(OnClickListener listener) {
- mManageButton.setOnClickListener(listener);
+ mManageOrHistoryButton.setOnClickListener(listener);
}
/** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@
/** Show "History" instead of "Manage" on the start button. */
public void showHistory(boolean showHistory) {
+ FooterViewRefactor.assertInLegacyMode();
if (mShowHistory == showHistory) {
return;
}
@@ -260,17 +301,13 @@
}
private void updateContent() {
- if (mShowHistory) {
- mManageButton.setText(mManageNotificationHistoryText);
- mManageButton.setContentDescription(mManageNotificationHistoryText);
- } else {
- mManageButton.setText(mManageNotificationText);
- mManageButton.setContentDescription(mManageNotificationText);
- }
if (FooterViewRefactor.isEnabled()) {
updateClearAllButtonText();
updateClearAllButtonDescription();
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
+
updateMessageString();
updateMessageIcon();
} else {
@@ -285,6 +322,14 @@
// `updateResources`, which will eventually be removed. There are, however, still
// situations in which we want to update the views even if the resource IDs didn't
// change, such as configuration changes.
+ if (mShowHistory) {
+ mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+ } else {
+ mManageOrHistoryButton.setText(mManageNotificationText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+ }
+
mClearAllButton.setText(R.string.clear_all_notifications_text);
mClearAllButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@
/** Whether the start button shows "History" (true) or "Manage" (false). */
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mShowHistory;
}
@@ -304,7 +350,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateColors();
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
}
@@ -328,23 +376,22 @@
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
- mManageButton.setBackground(manageBg);
- mManageButton.setTextColor(onSurface);
+ mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setTextColor(onSurface);
mSeenNotifsFooterTextView.setTextColor(onSurface);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
private void updateResources() {
+ FooterViewRefactor.assertInLegacyMode();
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- if (!FooterViewRefactor.isEnabled()) {
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
- }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96..9fb453a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [FooterView] to its [view model][FooterViewModel]. */
object FooterViewBinder {
- fun bind(
+ fun bindWhileAttached(
footer: FooterView,
viewModel: FooterViewModel,
clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
): DisposableHandle {
+ return footer.repeatWhenAttached {
+ lifecycleScope.launch {
+ bind(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ }
+ }
+
+ suspend fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener
+ ) = coroutineScope {
+ launch {
+ bindClearAllButton(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ )
+ }
+ launch {
+ bindManageOrHistoryButton(
+ footer,
+ viewModel,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ launch { bindMessage(footer, viewModel) }
+ }
+
+ private suspend fun bindClearAllButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ ) = coroutineScope {
+ footer.setClearAllButtonClickListener(clearAllNotifications)
+
+ launch {
+ viewModel.clearAllButton.labelId.collect { textId ->
+ footer.setClearAllButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setClearAllButtonDescription(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.isVisible.collect { isVisible ->
+ if (isVisible.isAnimating) {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ true,
+ ) { _ ->
+ isVisible.stopAnimating()
+ }
+ } else {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ false,
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun bindManageOrHistoryButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
+ ) = coroutineScope {
+ launch {
+ viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ footer.setManageButtonClickListener(launchNotificationHistory)
+ } else {
+ footer.setManageButtonClickListener(launchNotificationSettings)
+ }
+ }
+ }
+
+ launch {
+ viewModel.manageOrHistoryButton.labelId.collect { textId ->
+ footer.setManageOrHistoryButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setManageOrHistoryButtonDescription(textId)
+ }
+ }
+
+ // NOTE: The manage/history button is always visible as long as the footer is visible, no
+ // need to update the visibility here.
+ }
+
+ private suspend fun bindMessage(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ) = coroutineScope {
// Bind the resource IDs
footer.setMessageString(viewModel.message.messageId)
footer.setMessageIcon(viewModel.message.iconId)
- footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
- footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
- // Bind the click listeners
- footer.setClearAllButtonClickListener(clearAllNotifications)
-
- // Listen for visibility changes when the view is attached.
- return footer.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.clearAllButton.isVisible.collect { isVisible ->
- if (isVisible.isAnimating) {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ true,
- ) { _ ->
- isVisible.stopAnimating()
- }
- } else {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ false,
- )
- }
- }
- }
-
- lifecycleScope.launch {
- viewModel.message.isVisible.collect { visible ->
- footer.setFooterLabelVisible(visible)
- }
- }
+ launch {
+ viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a..691dc42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.Flow
data class FooterButtonViewModel(
- @StringRes val labelId: Int,
- @StringRes val accessibilityDescriptionId: Int,
+ @StringRes val labelId: Flow<Int>,
+ @StringRes val accessibilityDescriptionId: Flow<Int>,
val isVisible: Flow<AnimatedValue<Boolean>>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abc..5111c11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
class FooterViewModel(
activeNotificationsInteractor: ActiveNotificationsInteractor,
+ notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
- labelId = R.string.clear_all_notifications_text,
- accessibilityDescriptionId = R.string.accessibility_clear_all,
+ labelId = flowOf(R.string.clear_all_notifications_text),
+ accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
activeNotificationsInteractor.hasClearableNotifications
.sample(
@@ -59,6 +65,22 @@
.toAnimatedValueFlow(),
)
+ val manageButtonShouldLaunchHistory =
+ notificationSettingsInteractor.isNotificationHistoryEnabled
+
+ private val manageOrHistoryButtonText: Flow<Int> =
+ manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+ if (shouldLaunchHistory) R.string.manage_notifications_history_text
+ else R.string.manage_notifications_text
+ }
+
+ val manageOrHistoryButton: FooterButtonViewModel =
+ FooterButtonViewModel(
+ labelId = manageOrHistoryButtonText,
+ accessibilityDescriptionId = manageOrHistoryButtonText,
+ isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+ )
+
val message: FooterMessageViewModel =
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@
@SysUISingleton
fun provideOptional(
activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+ notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
shadeInteractor: Provider<ShadeInteractor>,
): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@
Optional.of(
FooterViewModel(
activeNotificationsInteractor.get(),
+ notificationSettingsInteractor.get(),
seenNotificationsInteractor.get(),
shadeInteractor.get()
)
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 b9afb14..5e0110b 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
@@ -4698,6 +4698,7 @@
* this will return false.
**/
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mFooterView != null && mFooterView.isHistoryShown();
}
@@ -4710,10 +4711,10 @@
}
mFooterView = footerView;
addView(mFooterView, index);
- if (mManageButtonClickListener != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
if (!FooterViewRefactor.isEnabled()) {
+ if (mManageButtonClickListener != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
mFooterView.setClearAllButtonClickListener(v -> {
if (mFooterClearAllListener != null) {
mFooterClearAllListener.onClearAll();
@@ -4794,8 +4795,8 @@
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- mFooterView.showHistory(showHistory);
if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.showHistory(showHistory);
mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
@@ -5490,6 +5491,7 @@
* Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
*/
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+ FooterViewRefactor.assertInLegacyMode();
mManageButtonClickListener = listener;
if (mFooterView != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
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 ed26677..a2ff406 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
@@ -21,8 +21,9 @@
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.nsslFalsingFix;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +846,13 @@
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
- mView.setManageButtonClickListener(v -> {
- if (mNotificationActivityStarter != null) {
- mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
- }
- });
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setManageButtonClickListener(v -> {
+ if (mNotificationActivityStarter != null) {
+ mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+ }
+ });
+ }
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -2052,7 +2055,7 @@
}
boolean horizontalSwipeWantsIt = false;
boolean scrollerWantsIt = false;
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) {
// Reverse the order relative to the else statement. onScrollTouch will reset on an
// UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
if (mLongPressedView == null && !mView.isBeingDragged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e..4d65b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
+ private val loggerOptional: Optional<NotificationStatsLogger>,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
- private val loggerOptional: Optional<NotificationStatsLogger>,
+ // Using a provider to avoid a circular dependency.
+ private val notificationActivityStarter: Provider<NotificationActivityStarter>,
private val viewModel: NotificationListViewModel,
) {
@@ -115,7 +119,7 @@
) { footerView: FooterView ->
traceSection("bind FooterView") {
val disposableHandle =
- FooterViewBinder.bind(
+ FooterViewBinder.bindWhileAttached(
footerView,
footerViewModel,
clearAllNotifications = {
@@ -124,6 +128,16 @@
)
parentView.clearAllNotifications()
},
+ launchNotificationSettings = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ false)
+ },
+ launchNotificationHistory = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ true)
+ },
)
parentView.setFooterView(footerView)
return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index b0192c0..11e374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
new file mode 100644
index 0000000..ab6a37b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.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.util.kotlin
+
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+object SharedPreferencesExt {
+ /**
+ * Returns a flow of [Unit] that is invoked each time shared preference is updated.
+ *
+ * @param key Optional key to limit updates to a particular key.
+ */
+ fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
+ conflatedCallbackFlow {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
+ registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a..ff1daea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger
+import android.content.Context
import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@
@Provides
fun provideAudioRepository(
+ @Application context: Context,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
- ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+ ): AudioRepository =
+ AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
@Provides
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 8f0e910..8fbeb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
@@ -24,11 +25,13 @@
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -167,6 +170,28 @@
listener.onAuthenticationStopped()
assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
}
+
+ @Test
+ fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+ testScope.runTest {
+ val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+ runCurrent()
+
+ val listener = biometricManager.captureListener()
+ listener.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT,
+ REASON_AUTH_BP,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+
+ assertThat(fingerprintAcquiredStatus)
+ .isEqualTo(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ }
}
private fun BiometricManager.captureListener() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index d7b7d79..5c34fd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,12 +19,14 @@
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
+import android.hardware.biometrics.BiometricFingerprintConstants
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -162,6 +164,27 @@
)
assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
}
+
+ @Test
+ fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+ testScope.runTest {
+ val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+ runCurrent()
+
+ biometricStatusRepository.setFingerprintAcquiredStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ assertThat(fingerprintAcquiredStatus)
+ .isEqualTo(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ }
}
private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 3603c3c..5509c04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,6 +58,7 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -100,6 +101,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -253,7 +255,8 @@
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
- mock(),
+ biometricStatusInteractor,
+ kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
kosmos.dozeServiceHost,
kosmos.keyguardInteractor,
@@ -426,6 +429,54 @@
}
}
+ // On progress bar shown - hide indicator
+ // On progress bar hidden - show indicator
+ @Test
+ fun verifyIndicatorProgressBarInteraction() {
+ testScope.runTest {
+ // Pre-auth conditions
+ setupTestConfiguration(
+ DeviceConfig.X_ALIGNED,
+ rotation = DisplayRotation.ROTATION_0,
+ isInRearDisplayMode = false
+ )
+ biometricStatusRepository.setFingerprintAuthenticationReason(
+ AuthenticationReason.NotRunning
+ )
+ sideFpsProgressBarViewModel.setVisible(false)
+
+ // Show primary bouncer
+ updatePrimaryBouncer(
+ isShowing = true,
+ isAnimatingAway = false,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = true
+ )
+ runCurrent()
+
+ val inOrder = inOrder(windowManager)
+
+ // Verify indicator shown
+ inOrder.verify(windowManager).addView(any(), any())
+
+ // Set progress bar visible
+ sideFpsProgressBarViewModel.setVisible(true)
+
+ runCurrent()
+
+ // Verify indicator hidden
+ inOrder.verify(windowManager).removeView(any())
+
+ // Set progress bar invisible
+ sideFpsProgressBarViewModel.setVisible(false)
+
+ runCurrent()
+
+ // Verify indicator shown
+ inOrder.verify(windowManager).addView(any(), any())
+ }
+ }
+
private fun updatePrimaryBouncer(
isShowing: Boolean,
isAnimatingAway: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6a9c881..2e94d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -81,6 +81,7 @@
private const val CHALLENGE = 2L
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -1246,6 +1247,14 @@
}
@Test
+ fun logoIsNullIfPackageNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isNull()
+ }
+
+ @Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
@@ -1291,7 +1300,8 @@
contentView: PromptContentView? = null,
logoRes: Int = -1,
logoBitmap: Bitmap? = null,
- block: suspend TestScope.() -> Unit
+ packageName: String = OP_PACKAGE_NAME,
+ block: suspend TestScope.() -> Unit,
) {
selector.initializePrompt(
requireConfirmation = testCase.confirmationRequested,
@@ -1302,6 +1312,7 @@
contentViewFromApp = contentView,
logoResFromApp = logoRes,
logoBitmapFromApp = logoBitmap,
+ packageName = packageName,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1481,6 +1492,7 @@
contentViewFromApp: PromptContentView? = null,
logoResFromApp: Int = -1,
logoBitmapFromApp: Bitmap? = null,
+ packageName: String = OP_PACKAGE_NAME,
) {
val info =
PromptInfo().apply {
@@ -1500,7 +1512,7 @@
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
- OP_PACKAGE_NAME,
+ packageName,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 3c43031..2014755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics.ui.viewmodel
-import android.app.ActivityTaskManager
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.Color
@@ -39,10 +38,10 @@
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -80,7 +79,6 @@
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -109,7 +107,6 @@
private val kosmos = testKosmos()
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock
private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@@ -147,7 +144,6 @@
context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400)
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private lateinit var biometricStatusInteractor: BiometricStatusInteractor
private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
private lateinit var displayStateInteractor: DisplayStateInteractorImpl
private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@@ -184,6 +180,7 @@
.thenReturn(
Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
)
+ kosmos.biometricStatusRepository = biometricStatusRepository
alternateBouncerInteractor =
AlternateBouncerInteractor(
@@ -197,9 +194,6 @@
testScope.backgroundScope,
)
- biometricStatusInteractor =
- BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
-
displayStateInteractor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
@@ -256,6 +250,7 @@
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
+ kosmos.biometricStatusInteractor,
kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
kosmos.dozeServiceHost,
@@ -263,13 +258,13 @@
displayStateInteractor,
kosmos.testDispatcher,
testScope.backgroundScope,
- kosmos.powerInteractor,
+ kosmos.powerInteractor
)
underTest =
SideFpsOverlayViewModel(
mContext,
- biometricStatusInteractor,
+ kosmos.biometricStatusInteractor,
deviceEntrySideFpsOverlayInteractor,
displayStateInteractor,
sfpsSensorInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c98d537..de455f63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,11 +34,12 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -69,12 +70,13 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
-import java.util.*
+import java.util.Optional
import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@Mock
private lateinit var uiController: ControlsUiController
@@ -109,8 +111,6 @@
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
- private val preferredPanelRepository = FakeSelectedComponentRepository()
-
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -171,7 +171,7 @@
wrapper,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -225,7 +225,7 @@
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -245,7 +245,7 @@
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 4828ba3..18ce4a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -18,36 +18,40 @@
package com.android.systemui.controls.panels
import android.content.SharedPreferences
+import android.content.pm.UserInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.File
+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.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
- @Mock private lateinit var userTracker: UserTracker
+ private lateinit var userTracker: FakeUserTracker
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
- whenever(userTracker.userId).thenReturn(0)
+ userTracker = kosmos.fakeUserTracker.apply { set(listOf(PRIMARY_USER, SECONDARY_USER), 0) }
}
@Test
@@ -91,7 +95,7 @@
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
- whenever(userTracker.userId).thenReturn(1)
+ userTracker.set(listOf(SECONDARY_USER), 0)
assertThat(repository.getAuthorizedPanels()).isEmpty()
}
@@ -127,6 +131,51 @@
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
+ @Test
+ fun observeAuthorizedPanels() =
+ testScope.runTest {
+ val sharedPrefs = FakeSharedPreferences()
+ val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(PRIMARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+
+ repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+ }
+
+ @Test
+ fun observeAuthorizedPanelsForAnotherUser() =
+ testScope.runTest {
+ val fileManager =
+ FakeUserFileManager(
+ mapOf(
+ 0 to FakeSharedPreferences(),
+ 1 to FakeSharedPreferences(),
+ )
+ )
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(SECONDARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Primary user is active, add authorized panels.
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Make secondary user active and add authorized panels again.
+ userTracker.set(listOf(PRIMARY_USER, SECONDARY_USER), 1)
+ assertThat(authorizedPanels).isEmpty()
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+ }
+
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
}
@@ -153,5 +202,9 @@
private const val FILE_NAME = "controls_prefs"
private const val KEY = "authorized_panels"
private const val TEST_PACKAGE = "package"
+ private val PRIMARY_USER =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ private val SECONDARY_USER =
+ UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index b463adf..a7e7ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -23,8 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserFileManager
@@ -74,7 +72,6 @@
@Mock private lateinit var userTracker: UserTracker
private lateinit var userFileManager: UserFileManager
- private val featureFlags = FakeFeatureFlags()
// under test
private lateinit var repository: SelectedComponentRepository
@@ -95,11 +92,9 @@
)
repository =
SelectedComponentRepositoryImpl(
- userFileManager,
- userTracker,
- featureFlags,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
bgDispatcher = testDispatcher,
- applicationScope = applicationCoroutineScope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bcef67e..94ea799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -38,8 +38,8 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -87,7 +87,7 @@
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
+ private lateinit var preferredPanelsRepository: SelectedComponentRepository
private lateinit var fakeExecutor: FakeExecutor
@@ -99,7 +99,7 @@
whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
fakeExecutor = FakeExecutor(FakeSystemClock())
- preferredPanelsRepository = FakeSelectedComponentRepository()
+ preferredPanelsRepository = kosmos.selectedComponentRepository
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 36ae0c7..8f3813d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -43,8 +43,8 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
@@ -53,6 +53,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -85,6 +86,8 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsUiControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock lateinit var controlsController: ControlsController
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@@ -100,7 +103,7 @@
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
- private val preferredPanelRepository = FakeSelectedComponentRepository()
+ private val preferredPanelRepository = kosmos.selectedComponentRepository
private lateinit var fakeDialogController: FakeSystemUIDialogController
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b..0d1e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
+ @Mock
private ViewTreeObserver mViewTreeObserver;
NavBarHelper mNavBarHelper;
@Mock
@@ -596,7 +598,8 @@
mUserContextProvider,
mWakefulnessLifecycle,
mTaskStackChangeListeners,
- new FakeDisplayTracker(mContext)));
+ new FakeDisplayTracker(mContext),
+ mNavBarButtonClickLogger));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349be..0dd988d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@
underTest.setShowNotificationsOnLockscreenEnabled(false)
assertThat(showNotifs).isEqualTo(false)
}
+
+ @Test
+ fun testGetIsNotificationHistoryEnabled() =
+ testScope.runTest {
+ val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 1,
+ )
+ assertThat(historyEnabled).isEqualTo(true)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 0,
+ )
+ assertThat(historyEnabled).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d..7d99d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3a..cac4a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.footer.ui.view;
import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryShown() {
mView.showHistory(true);
assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryNotShown() {
mView.showHistory(false);
assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonText(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getText().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonText(resId);
+ mView.setManageOrHistoryButtonText(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.manage_notifications_history_text;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonDescription(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getContentDescription().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonDescription(resId);
+ mView.setManageOrHistoryButtonDescription(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.accessibility_clear_all;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.clear_all_notifications_text;
mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@
public void testSetClearAllButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.clear_all_notifications_text;
- assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonText(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -178,7 +239,7 @@
public void testSetClearAllButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
- assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -206,7 +267,7 @@
public void testSetMessageString_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.unlock_to_see_notif_text;
- assertLogsWtf(()-> mView.setMessageString(resId));
+ assertLogsWtf(() -> mView.setMessageString(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -231,7 +292,7 @@
public void testSetMessageIcon_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.drawable.ic_friction_lock_closed;
- assertLogsWtf(()-> mView.setMessageIcon(resId));
+ assertLogsWtf(() -> mView.setMessageIcon(resId));
verify(mSpyContext, never()).getDrawable(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 8ab13f5..620d972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -14,109 +14,61 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class FooterViewModelTest : SysuiTestCase() {
- private lateinit var footerViewModel: FooterViewModel
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val configurationRepository: FakeConfigurationRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val shadeRepository = kosmos.shadeRepository
+ private val powerRepository = kosmos.powerRepository
+ private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
- private val dozeParameters: DozeParameters = mock()
-
- private val testComponent: TestComponent =
- DaggerFooterViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParameters,
- )
- )
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- // The underTest in the component is Optional, because that matches the provider we
- // currently have for the footer view model.
- footerViewModel = testComponent.underTest.get()
- }
+ val underTest = kosmos.footerViewModel
@Test
fun testMessageVisible_whenFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -125,8 +77,8 @@
@Test
fun testMessageVisible_whenNoFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
@@ -135,8 +87,8 @@
@Test
fun testClearAllButtonVisible_whenHasClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -153,8 +105,8 @@
@Test
fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -171,12 +123,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is expanded
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(1f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@
@Test
fun testClearAllButtonAnimating_whenShadeNotExpanded() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is collapsed
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(0f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@
// THEN button visibility should not animate
assertThat(visible?.isAnimating).isFalse()
}
+
+ @Test
+ fun testManageButton_whenHistoryDisabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ // THEN label is "Manage"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+ }
+
+ @Test
+ fun testHistoryButton_whenHistoryEnabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ // THEN label is "History"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f2..7589a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d..88e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
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
@@ -62,46 +53,18 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class NotificationListViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- UnfoldTransitionModule.Bindings::class,
- NotificationStatsLoggerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<NotificationListViewModel> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val zenModeRepository: FakeZenModeRepository
- val configurationController: FakeConfigurationController
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val fakeConfigurationController = kosmos.fakeConfigurationController
- private val testComponent: TestComponent =
- DaggerNotificationListViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks = TestMocksModule()
- )
+ val underTest = kosmos.notificationListViewModel
@Before
fun setUp() {
@@ -110,11 +73,11 @@
@Test
fun testIsImportantForAccessibility_falseWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -129,11 +92,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -148,11 +111,11 @@
@Test
fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN not on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
testScope,
@@ -167,7 +130,7 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
@@ -180,7 +143,7 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -193,13 +156,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.legacyQsFullscreen.value = true
+ fakeShadeRepository.legacyQsFullscreen.value = true
runCurrent()
// THEN should not show
@@ -208,16 +171,16 @@
@Test
fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.setQsExpansion(1f)
+ fakeShadeRepository.setQsExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationController.notifyConfigurationChanged()
+ fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
// THEN should show
@@ -226,13 +189,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND transitioning to AOD
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@
@Test
fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND is on bouncer
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
testScope,
@@ -267,7 +230,7 @@
@Test
fun testAreNotificationsHiddenInShade_true() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@
@Test
fun testAreNotificationsHiddenInShade_false() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@
@Test
fun testHasFilteredOutSeenNotifications_true() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@
@Test
fun testHasFilteredOutSeenNotifications_false() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 9d53e7d..9919c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -24,7 +24,7 @@
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
index 961022f..a4f28f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -20,4 +20,4 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
+var Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
index 1c8bd3b..e9b7a69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -18,9 +18,12 @@
package com.android.systemui.biometrics.data.repository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeBiometricStatusRepository : BiometricStatusRepository {
private val _fingerprintAuthenticationReason =
@@ -28,7 +31,16 @@
override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
_fingerprintAuthenticationReason.asStateFlow()
+ private val _fingerprintAcquiredStatus =
+ MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ _fingerprintAcquiredStatus.asStateFlow().filterNotNull()
+
fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
_fingerprintAuthenticationReason.value = reason
}
+
+ fun setFingerprintAcquiredStatus(status: FingerprintAuthenticationStatus) {
+ _fingerprintAcquiredStatus.value = status
+ }
}
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 3ea3ccf..1884a32 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
@@ -28,7 +28,7 @@
fun mediaActive(timestamp: Long = 0L) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = timestamp,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
new file mode 100644
index 0000000..109e113
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.panels
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.authorizedPanelsRepository: AuthorizedPanelsRepository by
+ Kosmos.Fixture {
+ AuthorizedPanelsRepositoryImpl(applicationContext, fakeUserFileManager, fakeUserTracker)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
deleted file mode 100644
index a231212..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ /dev/null
@@ -1,74 +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.controls.panels
-
-import android.os.UserHandle
-import com.android.systemui.kosmos.Kosmos
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSelectedComponentRepository : SelectedComponentRepository {
- private var shouldAddDefaultPanel: Boolean = true
- private val _selectedComponentFlows =
- mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
- private var currentUserHandle: UserHandle = UserHandle.of(0)
-
- override fun selectedComponentFlow(
- userHandle: UserHandle
- ): Flow<SelectedComponentRepository.SelectedComponent?> {
- // Return an existing flow for the user or create a new one
- return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
- MutableStateFlow(null)
- }
- }
-
- override fun getSelectedComponent(
- userHandle: UserHandle
- ): SelectedComponentRepository.SelectedComponent? {
- return _selectedComponentFlows[getUserHandle(userHandle)]?.value
- }
-
- override fun setSelectedComponent(
- selectedComponent: SelectedComponentRepository.SelectedComponent
- ) {
- val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
- flow.value = selectedComponent
- }
-
- override fun removeSelectedComponent() {
- _selectedComponentFlows[currentUserHandle]?.value = null
- }
- override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
-
- override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
- shouldAddDefaultPanel = shouldAdd
- }
-
- fun setCurrentUserHandle(userHandle: UserHandle) {
- currentUserHandle = userHandle
- }
- private fun getUserHandle(userHandle: UserHandle): UserHandle {
- return if (userHandle == UserHandle.CURRENT) {
- currentUserHandle
- } else {
- userHandle
- }
- }
-}
-
-val Kosmos.selectedComponentRepository by
- Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
new file mode 100644
index 0000000..ee5b7e5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.panels
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.selectedComponentRepository: SelectedComponentRepository by
+ Kosmos.Fixture {
+ SelectedComponentRepositoryImpl(fakeUserFileManager, fakeUserTracker, testDispatcher)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
new file mode 100644
index 0000000..207c3f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.settings
+
+import android.content.SharedPreferences
+import com.android.systemui.util.FakeSharedPreferences
+import java.io.File
+
+class FakeUserFileManager : UserFileManager {
+ private val sharedPreferences = mutableMapOf<SharedPrefKey, FakeSharedPreferences>()
+
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException("getFile not implemented in fake")
+ }
+
+ override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+ val key = SharedPrefKey(fileName, mode, userId)
+ return sharedPreferences.getOrPut(key) { FakeSharedPreferences() }
+ }
+
+ private data class SharedPrefKey(val fileName: String, val mode: Int, val userId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
new file mode 100644
index 0000000..4d7a40a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserFileManager by Kosmos.Fixture { FakeUserFileManager() }
+var Kosmos.userFileManager: UserFileManager by Kosmos.Fixture { fakeUserFileManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index e13fa52..82e0b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,6 +45,7 @@
val Kosmos.shadeControllerSceneImpl by
Kosmos.Fixture {
ShadeControllerSceneImpl(
+ mainDispatcher = testDispatcher,
scope = applicationCoroutineScope,
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca0..01cac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
activeNotificationsInteractor = activeNotificationsInteractor,
+ notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04d..489598c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
+ loggerOptional = Optional.of(notificationStatsLogger),
metricsLogger = metricsLogger,
hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
- loggerOptional = Optional.of(notificationStatsLogger),
+ notificationActivityStarter = { notificationActivityStarter },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e6..d4e9bfb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+ Kosmos.Fixture { fakeConfigurationController }
val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 3002299..fc6a800 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -24,7 +24,7 @@
@SysUISingleton
class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
- private val _isDeviceProvisioned = MutableStateFlow(false)
+ private val _isDeviceProvisioned = MutableStateFlow(true)
override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
private val _isFactoryResetProtectionActive = MutableStateFlow(false)
override val isFactoryResetProtectionActive: Flow<Boolean> = _isFactoryResetProtectionActive
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 36d398c..83fda9e 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -34,6 +34,8 @@
re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+DRY_RUN = "-n" in sys.argv
+
ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
@@ -46,7 +48,7 @@
stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
stats_method = collections.defaultdict()
-with open(sys.argv[1]) as f:
+with open(sys.argv[-1]) as f:
for line in f.readlines():
result = re_result.search(line)
if result:
@@ -67,7 +69,7 @@
clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
for root, dirs, files in os.walk("."):
for f in files:
- if clazz_match.match(f):
+ if clazz_match.match(f) and not DRY_RUN:
path = os.path.join(root, f)
subprocess.run(["sed", "-i", "-E", SED_ARG, path])
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3670459..8af561f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,7 +16,9 @@
package android.platform.test.ravenwood;
+import android.app.ActivityManager;
import android.app.Instrumentation;
+import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
@@ -30,10 +32,12 @@
import java.io.PrintStream;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -50,11 +54,27 @@
private static ScheduledFuture<?> sPendingTimeout;
+ /**
+ * When set, an unhandled exception was discovered (typically on a background thread), and we
+ * capture it here to ensure it's reported as a test failure.
+ */
+ private static final AtomicReference<Throwable> sPendingUncaughtException =
+ new AtomicReference<>();
+
+ private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
+ (thread, throwable) -> {
+ // Remember the first exception we discover
+ sPendingUncaughtException.compareAndSet(null, throwable);
+ };
+
public static boolean isOnRavenwood() {
return true;
}
public static void init(RavenwoodRule rule) {
+ maybeThrowPendingUncaughtException(false);
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+
RuntimeInit.redirectLogStreams();
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
@@ -64,6 +84,8 @@
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
+
com.android.server.LocalServices.removeAllServicesForTest();
if (rule.mProvideMainThread) {
@@ -78,6 +100,10 @@
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
+
+ // Touch some references early to ensure they're <clinit>'ed
+ Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.VERSION.SDK);
}
public static void reset(RavenwoodRule rule) {
@@ -94,9 +120,13 @@
com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
+
+ maybeThrowPendingUncaughtException(true);
}
public static void logTestRunner(String label, Description description) {
@@ -120,4 +150,21 @@
}
out.println("-----END ALL THREAD STACKS-----");
}
+
+ /**
+ * If there's a pending uncaught exception, consume and throw it now. Typically used to
+ * report an exception on a background thread as a failure for the currently running test.
+ */
+ private static void maybeThrowPendingUncaughtException(boolean duringReset) {
+ final Throwable pending = sPendingUncaughtException.getAndSet(null);
+ if (pending != null) {
+ if (duringReset) {
+ throw new IllegalStateException(
+ "Found an uncaught exception during this test", pending);
+ } else {
+ throw new IllegalStateException(
+ "Found an uncaught exception before this test started", pending);
+ }
+ }
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b38..daed457 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,10 @@
package android.platform.test.ravenwood;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static org.junit.Assert.fail;
import android.platform.test.annotations.DisabledOnRavenwood;
@@ -94,12 +98,12 @@
}
}
- private static final int SYSTEM_UID = 1000;
private static final int NOBODY_UID = 9999;
- private static final int FIRST_APPLICATION_UID = 10000;
private static final AtomicInteger sNextPid = new AtomicInteger(100);
+ int mCurrentUser = USER_SYSTEM;
+
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
* tests its own unique PID.
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e49b64e..a5ecd20 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -82,6 +82,8 @@
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
android.os.UserManager
+android.os.VibrationAttributes
+android.os.VibrationAttributes$Builder
android.os.WorkSource
android.content.ClipData
@@ -144,6 +146,7 @@
android.content.ContentProvider
+android.app.ActivityManager
android.app.Instrumentation
android.metrics.LogMaker
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be33..82d9377 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+ private final NightMode mNightMode = new NightMode(){
+ private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+ @Override
+ public int get() {
+ return mNightModeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mNightModeValue = mode;
+ if (enableNightModeCache()) {
+ UiModeManager.invalidateNightModeCache();
+ }
+ }
+ };
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@
@Override
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+ if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -392,7 +409,7 @@
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
+ mNightMode.get(), 0);
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
@@ -412,7 +429,7 @@
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
- if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+ if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
verifySetupWizardCompleted();
synchronized (mLock) {
@@ -471,12 +488,12 @@
verifySetupWizardCompleted();
final Resources res = context.getResources();
+ mNightMode.set(res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode));
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
- mNightMode = res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@
private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
persistComputedNightMode(mCurrentUser);
}
}
@@ -585,7 +602,7 @@
}
private void updateCustomTimeLocked() {
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -606,9 +623,9 @@
return;
}
if (mSetupWizardComplete) {
- mNightMode = Secure.getIntForUser(context.getContentResolver(),
+ mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode), userId);
+ com.android.internal.R.integer.config_defaultNightMode), userId));
mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
}
@@ -834,21 +851,23 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (mNightMode != mode || mNightModeCustomType != customModeType) {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
cancelCustomAlarm();
}
mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
- mNightMode = mode;
+ mNightMode.set(mode);
//deactivates AttentionMode if user toggles DarkTheme
mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
- if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+ if ((mNightMode.get() != MODE_NIGHT_AUTO
+ && mNightMode.get() != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -865,7 +884,7 @@
@Override
public int getNightMode() {
synchronized (mLock) {
- return mNightMode;
+ return mNightMode.get();
}
}
@@ -999,18 +1018,19 @@
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
mOverrideNightModeOff = !active;
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
&& active) {
- mNightMode = UiModeManager.MODE_NIGHT_YES;
- } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+ mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
&& !active) {
- mNightMode = UiModeManager.MODE_NIGHT_NO;
+ mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
updateConfigurationLocked();
applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@
private void onCustomTimeUpdated(int user) {
persistNightMode(user);
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -1431,8 +1451,8 @@
pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
- pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" (");
- pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+ pw.print(" mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+ pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.UI_NIGHT_MODE, mNightMode.get(), user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@
uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
}
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+ if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+ updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
}
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
boolean activateNightMode = mComputedNightMode;
if (mTwilightManager != null) {
mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@
}
}
- if (mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
} else {
@@ -2010,7 +2030,7 @@
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
- if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@
mComputedNightMode = newComputedValue;
}
- if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+ if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
}
@@ -2279,4 +2299,13 @@
Sandman.startDreamWhenDockedIfAppropriate(context);
}
}
+
+ /**
+ * Interface to contain the value for system night mode. We make the night mode accessible
+ * through this class to ensure that the reassignment of this value invalidates the cache.
+ */
+ private interface NightMode {
+ int get();
+ void set(int mode);
+ }
}
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed..856a15f 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@
// Ensure that the current activity supports assist data
boolean isAssistDataAllowed = false;
try {
- isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+ isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
} catch (RemoteException e) {
// Should never happen
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b5..f1eea72 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@
return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mAudioService.isSADevice(deviceState);
+ }
+
//------------------------------------------------
// for testing purposes only
void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a..102a960 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@
&& di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@
&& di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
-
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+ ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e..bbbba26 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@
sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
- + " -> " + newDevice));
+ + " -> " + newDevice).printLog(TAG));
AudioDeviceBroker.BtDeviceChangedData data =
new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
"AudioService");
@@ -10638,6 +10638,10 @@
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mSpatializerHelper.isSADevice(deviceState);
+ }
+
private boolean isBluetoothPrividged() {
if (!bluetoothMacAddressAnonymization()) {
return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f12..8d76731 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@
return false;
}
- private boolean isSADevice(AdiDeviceState deviceState) {
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 1ae4d64..1dc882e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.hardware.biometrics.AuthenticationStateListener;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -115,7 +117,7 @@
* @param userId The user Id for the requested authentication
*/
public void onAuthenticationFailed(int requestReason, int userId) {
- for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ for (AuthenticationStateListener listener : mAuthenticationStateListeners) {
try {
listener.onAuthenticationFailed(requestReason, userId);
} catch (RemoteException e) {
@@ -125,6 +127,27 @@
}
}
+ /**
+ * Defines behavior in response to biometric being acquired.
+ * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+ * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+ * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+ * a known acquired message.
+ */
+ public void onAuthenticationAcquired(
+ BiometricSourceType biometricSourceType, int requestReason,
+ @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
+ ) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationAcquired(biometricSourceType, requestReason, acquiredInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "stopped", e);
+ }
+ }
+ }
+
@Override
public void binderDied() {
// Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index e0fd44b..8121a63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -102,6 +103,7 @@
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
+
public FingerprintAuthenticationClient(
@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@@ -280,6 +282,8 @@
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
// for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
+ mAuthenticationStateListeners.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
super.onAcquired(acquiredInfo, vendorCode);
PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 60c532c..b6311af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -201,6 +202,8 @@
@Override
public void onAcquired(int acquiredInfo, int vendorCode) {
+ mAuthenticationStateListeners.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
super.onAcquired(acquiredInfo, vendorCode);
@LockoutTracker.LockoutMode final int lockoutMode =
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a15cb10..a23c08a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -716,7 +716,10 @@
// Programmed
int programedInfo = params[offset] & 0x0F;
if (isValidProgrammedInfo(programedInfo)) {
- if (programedInfo == 0x09 || programedInfo == 0x0B) {
+ offset = offset + 1;
+ // Duration Available (2 bytes)
+ if ((programedInfo == 0x09 || programedInfo == 0x0B)
+ && params.length - offset >= 2) {
durationAvailable = true;
} else {
return true;
@@ -726,16 +729,17 @@
// Non programmed
int nonProgramedErrorInfo = params[offset] & 0x0F;
if (isValidNotProgrammedErrorInfo(nonProgramedErrorInfo)) {
- if (nonProgramedErrorInfo == 0x0E) {
+ offset = offset + 1;
+ // Duration Available (2 bytes)
+ if (nonProgramedErrorInfo == 0x0E && params.length - offset >= 2) {
durationAvailable = true;
} else {
return true;
}
}
}
- offset = offset + 1;
// Duration Available (2 bytes)
- if (durationAvailable && params.length - offset >= 2) {
+ if (durationAvailable) {
return (isValidDurationHours(params[offset]) && isValidMinute(params[offset + 1]));
}
return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0f2af31..fff4939 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3708,6 +3708,30 @@
return InputBindResult.INVALID_USER;
}
+ // Ensure that caller's focused window and display parameters are allowd to
+ // display input method.
+ final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+ windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
+ switch (imeClientFocus) {
+ case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
+ Slog.e(TAG,
+ "startInputOrWindowGainedFocusInternal: display ID mismatch.");
+ return InputBindResult.DISPLAY_ID_MISMATCH;
+ case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
+ // Check with the window manager to make sure this client actually
+ // has a window with focus. If not, reject. This is thread safe
+ // because if the focus changes some time before or after, the
+ // next client receiving focus that has any interest in input will
+ // be calling through here after that change happens.
+ if (DEBUG) {
+ Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
+ + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
+ }
+ return InputBindResult.NOT_IME_TARGET_WINDOW;
+ case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
+ return InputBindResult.INVALID_DISPLAY_ID;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
@@ -3756,26 +3780,6 @@
+ " imeDispatcher=" + imeDispatcher
+ " cs=" + cs);
}
- final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
- windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
- switch (imeClientFocus) {
- case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
- Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
- return InputBindResult.DISPLAY_ID_MISMATCH;
- case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
- // Check with the window manager to make sure this client actually
- // has a window with focus. If not, reject. This is thread safe
- // because if the focus changes some time before or after, the
- // next client receiving focus that has any interest in input will
- // be calling through here after that change happens.
- if (DEBUG) {
- Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
- + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
- }
- return InputBindResult.NOT_IME_TARGET_WINDOW;
- case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
- return InputBindResult.INVALID_DISPLAY_ID;
- }
final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
// In case mShowForced flag affects the next client to keep IME visible, when the current
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9..1c9bbab 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2049,7 +2049,9 @@
if (!mUserProfiles.isProfileUser(userId)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
- mZenModeHelper.onUserUnlocked(userId);
+ if (!android.app.Flags.modesApi()) {
+ mZenModeHelper.onUserUnlocked(userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 41ff415..2f20bbe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -328,6 +328,7 @@
}
}
+ // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
public void onUserUnlocked(int user) {
loadConfigForUser(user, "onUserUnlocked");
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dbff442..722654a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@
}
flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
name: "sensitive_notification_app_protection"
namespace: "systemui"
description: "This flag controls the sensitive notification app protections while screen sharing"
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51790b8..b947aa3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5016,6 +5016,8 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+ Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+ + mStylusButtonsEnabled);
if (mStylusButtonsEnabled) {
sendSystemKeyToStatusBarAsync(event);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957..37f3825 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@
for (Rect crop : relativeCropHints) {
Rect originalRect = new Rect(crop);
originalRect.scale(wallpaper.mSampleSize);
- originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
result.add(originalRect);
}
return result;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 1128d0c..2e0546e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,33 +1319,9 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r == null) return;
- final TransitionController controller = r.mTransitionController;
- if (!controller.isShellTransitionsEnabled()) {
+ if (r != null) {
r.setShowWhenLocked(showWhenLocked);
- return;
}
- if (controller.isCollecting()
- && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
- // Keyguard isn't locked, so this can be done as part of the collecting
- // transition.
- r.setShowWhenLocked(showWhenLocked);
- return;
- }
- final Transition transition = new Transition(
- showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
- 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
- r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
- transition.collect(r);
- r.setShowWhenLocked(showWhenLocked);
- if (transition.isNoOp()) {
- transition.abort();
- return;
- }
- controller.requestStartTransition(transition, null /* trigger */,
- null /* remoteTransition */, null /* displayChange */);
- transition.setReady(r, true);
- });
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -1358,34 +1334,9 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r == null) return;
- final TransitionController controller = r.mTransitionController;
- // If shell transitions is not enabled just set it directly.
- if (!controller.isShellTransitionsEnabled()) {
+ if (r != null) {
r.setInheritShowWhenLocked(inheritShowWhenLocked);
- return;
}
- if (controller.isCollecting()
- && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
- // Keyguard isn't locked, so this can be done as part of the collecting
- // transition.
- r.setInheritShowWhenLocked(inheritShowWhenLocked);
- return;
- }
- final Transition transition = new Transition(
- inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
- 0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
- r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
- transition.collect(r);
- r.setInheritShowWhenLocked(inheritShowWhenLocked);
- if (transition.isNoOp()) {
- transition.abort();
- return;
- }
- controller.requestStartTransition(transition, null /* trigger */,
- null /* remoteTransition */, null /* displayChange */);
- transition.setReady(r, true);
- });
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3d..0def5a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@
}
@Override
- public boolean isAssistDataAllowedOnCurrentActivity() {
+ public boolean isAssistDataAllowed() {
int userId;
+ boolean hasRestrictedWindow;
synchronized (mGlobalLock) {
final Task focusedRootTask = getTopDisplayFocusedRootTask();
if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@
return false;
}
userId = activity.mUserId;
+ DisplayContent displayContent = activity.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+ return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+ getUserManager().getProfileType(windowState.mShowUserId));
+ }, true /* traverseTopToBottom */);
}
- return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+ return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+ && !hasRestrictedWindow;
}
private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951..d08f043 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+ try {
+ doLogSentTransition(transition, targets);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogSentTransition(
+ Transition transition, ArrayList<Transition.ChangeInfo> targets) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -91,6 +102,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+ try {
+ doLogFinishTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogFinishTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -114,6 +134,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+ try {
+ doLogAbortedTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAbortedTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -131,6 +160,15 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+ try {
+ doLogRemovingStartingWindow(startingData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb4588..967f415 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
public class ScreenRecordingCallbackController {
@@ -57,10 +56,10 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+ private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+ private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
private final WindowManagerService mWms;
@@ -108,8 +107,8 @@
}
IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- IMediaProjectionManager mediaProjectionManager =
- IMediaProjectionManager.Stub.asInterface(binder);
+ IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+ binder);
long identityToken = Binder.clearCallingIdentity();
MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@
synchronized (mWms.mGlobalLock) {
IBinder binder = callback.asBinder();
Callback callbackInfo = mCallbacks.remove(binder);
+ if (callbackInfo == null) {
+ return;
+ }
binder.unlinkToDeath(callbackInfo, 0);
boolean uidHasCallback = false;
- for (Callback cb : mCallbacks.values()) {
- if (cb.mUid == callbackInfo.mUid) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
uidHasCallback = true;
break;
}
@@ -213,7 +215,9 @@
return;
}
- dispatchCallbacks(Set.of(uid), processVisible);
+ ArraySet<Integer> uidSet = new ArraySet<>();
+ uidSet.add(uid);
+ dispatchCallbacks(uidSet, processVisible);
}
@GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private Set<Integer> getRecordedUids() {
- Set<Integer> result = new ArraySet<>();
+ private ArraySet<Integer> getRecordedUids() {
+ ArraySet<Integer> result = new ArraySet<>();
if (mRecordedWC == null) {
return result;
}
@@ -248,37 +252,43 @@
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
if (uids.isEmpty()) {
return;
}
- for (Integer uid : uids) {
- mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ for (int i = 0; i < uids.size(); i++) {
+ mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
}
- for (Callback callback : mCallbacks.values()) {
- if (!uids.contains(callback.mUid)) {
- continue;
- }
- try {
- callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
- } catch (RemoteException e) {
- // Client has died. Cleanup is handled via DeathRecipient.
+ ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+ callbacks.add(mCallbacks.valueAt(i).mCallback);
}
}
+
+ mWms.mH.post(() -> {
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ });
}
void dump(PrintWriter pw) {
pw.format("ScreenRecordingCallbackController:\n");
pw.format(" Registered callbacks:\n");
- for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
- pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ pw.format(" callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
}
pw.format(" Last invoked states:\n");
- for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
- pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
- entry.getValue());
+ for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+ mLastInvokedStateByUid.valueAt(i));
}
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1e58306..2accf9a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,26 +2917,6 @@
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
}
- /**
- * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
- * only here for legacy usages where we can't tell ahead-of-time whether something will
- * generate a change.
- */
- boolean isNoOp() {
- for (int i = mParticipants.size() - 1; i >= 0; --i) {
- // This is the same criteria as the rejection logic in calculateTargets
- final WindowContainer<?> wc = mParticipants.valueAt(i);
- if (!wc.isAttached()) continue;
- // The level of transition target should be at least window token.
- if (wc.asWindowState() != null) continue;
- final ChangeInfo changeInfo = mChanges.get(wc);
- // Reject no-ops
- if (!changeInfo.hasChanged()) continue;
- return false;
- }
- return true;
- }
-
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b..debe794 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@
void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
requireOverlaySurfaceControl();
- mOverlays.add(p);
+
+ boolean hasExistingOverlay = false;
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+ hasExistingOverlay = true;
+ }
+ }
+ if (!hasExistingOverlay) {
+ mOverlays.add(p);
+ }
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 1688a1a..817901f 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -32,7 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Size;
@@ -159,10 +158,6 @@
private InputWindowHandle[] mLastWindowHandles;
- private final Object mIgnoredWindowTokensLock = new Object();
-
- private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
private void startHandlerThreadIfNeeded() {
synchronized (mHandlerThreadLock) {
if (mHandler == null) {
@@ -173,18 +168,6 @@
}
}
- void addIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.add(token);
- }
- }
-
- void removeIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.remove(token);
- }
- }
-
void registerListener(IBinder window, ITrustedPresentationListener listener,
TrustedPresentationThresholds thresholds, int id) {
startHandlerThreadIfNeeded();
@@ -271,12 +254,8 @@
ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
new ArrayMap<>();
- ArraySet<IBinder> ignoredWindowTokens;
- synchronized (mIgnoredWindowTokensLock) {
- ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
- }
for (var windowHandle : mLastWindowHandles) {
- if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+ if (!windowHandle.canOccludePresentation) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fc23700..554cbce 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
import android.Manifest;
import android.Manifest.permission;
import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -3285,7 +3286,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
/**
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
@@ -4513,7 +4514,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public SurfaceControl addShellRoot(int displayId, IWindow client,
@WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4532,7 +4533,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setShellRootAccessibilityWindow(int displayId,
@WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4555,7 +4556,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setDisplayWindowInsetsController(
int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4574,7 +4575,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void updateDisplayWindowRequestedVisibleTypes(
int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5834,7 +5835,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
setForcedDisplaySize_enforcePermission();
@@ -5852,7 +5853,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayScalingMode(int displayId, int mode) {
setForcedDisplayScalingMode_enforcePermission();
@@ -5940,7 +5941,7 @@
return changed;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplaySize(int displayId) {
clearForcedDisplaySize_enforcePermission();
@@ -6003,7 +6004,7 @@
return -1;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
setForcedDisplayDensityForUser_enforcePermission();
@@ -6029,7 +6030,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplayDensityForUser(int displayId, int userId) {
clearForcedDisplayDensityForUser_enforcePermission();
@@ -6529,7 +6530,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+ @EnforcePermission(android.Manifest.permission.STATUS_BAR)
public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
@@ -6571,7 +6572,7 @@
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
@Override
public Region getCurrentImeTouchRegion() {
getCurrentImeTouchRegion_enforcePermission();
@@ -8461,12 +8462,13 @@
SurfaceControlViewHost.SurfacePackage overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
- } else if (overlay.getSurfaceControl() == null
- || !overlay.getSurfaceControl().isValid()) {
- throw new IllegalArgumentException(
- "Invalid overlay surfacecontrol passed in for task=" + taskId);
}
synchronized (mGlobalLock) {
+ if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -9949,13 +9951,17 @@
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ registerScreenRecordingCallback_enforcePermission();
return mScreenRecordingCallbackController.register(callback);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ unregisterScreenRecordingCallback_enforcePermission();
mScreenRecordingCallbackController.unregister(callback);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 24e50c5..f5806c0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1148,10 +1148,6 @@
parentWindow.addChild(this, sWindowSubLayerComparator);
}
- if (token.mRoundedCornerOverlay) {
- mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
- getWindowToken());
- }
}
@Override
@@ -1163,6 +1159,9 @@
if (secureWindowState()) {
getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
}
+ // All apps should be considered as occluding when computing TrustedPresentation Thresholds.
+ final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
+ getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
}
void updateTrustedOverlay() {
@@ -2344,9 +2343,6 @@
mSession.onWindowRemoved(this);
mWmService.postWindowRemoveCleanupLocked(this);
- mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
- getWindowToken());
-
consumeInsetsChange();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f96d9e8..9cdaec6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -18,6 +18,7 @@
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -517,6 +518,16 @@
}
@Test
+ public void testAuthenticationStateListeners_onAuthenticationAcquired()
+ throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAcquired(FINGERPRINT_ACQUIRED_START, 0);
+
+ verify(mAuthenticationStateListeners).onAuthenticationAcquired(any(), anyInt(), anyInt());
+ }
+
+ @Test
public void testAuthenticationStateListeners_onAuthenticationSucceeded()
throws RemoteException {
mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b..ea84eb2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+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;
@@ -48,6 +49,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -118,6 +120,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ @Ignore
public void testBugreportFileManagerKeepFilesOnRetrieval() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b8dcecd..e27bb4c 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -320,4 +320,10 @@
mWindowInfosReportedListeners.add(listener);
return this;
}
+
+ @Override
+ public SurfaceControl.Transaction setCanOccludePresentation(SurfaceControl sc,
+ boolean canOccludePresentation) {
+ return this;
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7c..c247c08 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -174,6 +175,11 @@
@Before
+ public void disableProcessCaches() {
+ IpcDataCache.disableForTestMode();
+ }
+
+ @Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
mPermissionEnforcer = new FakePermissionEnforcer();
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 5d114f4..7462201 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5202,7 +5202,7 @@
// rules for a missing package, created a long time ago and deleted a long time ago
config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
- mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
.containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0..c2a5824 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@
private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
boolean assistScreenshotAllowed) throws Exception {
- doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+ doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
.noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index a2105b0..9ee48d8 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,6 +21,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -56,6 +57,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IVideoCallback;
import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -4019,9 +4021,12 @@
}
/**
+ * Retrieves the direction of this connection.
* @return The direction of the call.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public final @Call.Details.CallDirection int getCallDirection() {
return mCallDirection;
}