Merge "Refactoring metrics in CrashRecovery module" into main
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 aec2842..de6addf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -90,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";
@@ -27747,6 +27748,7 @@
method public void onRecordingTuned(@NonNull String, @NonNull android.net.Uri);
method public abstract void onRelease();
method public void onResetInteractiveApp();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onSelectedTrackInfo(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
method public void onSetTeletextAppEnabled(boolean);
method public void onSignalStrength(int);
@@ -27781,6 +27783,7 @@
method @CallSuper public void requestCurrentVideoBounds();
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
method @CallSuper public void requestStopRecording(@NonNull String);
@@ -27845,6 +27848,7 @@
method public void sendCurrentChannelUri(@Nullable android.net.Uri);
method public void sendCurrentTvInputId(@Nullable String);
method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendSelectedTrackInfo(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendStreamVolume(float);
method public void sendTimeShiftMode(int);
@@ -27880,6 +27884,7 @@
method public void onRequestCurrentVideoBounds(@NonNull String);
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
method public void onRequestStopRecording(@NonNull String, @NonNull String);
@@ -53784,6 +53789,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();
@@ -53793,6 +53799,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>);
@@ -53812,6 +53819,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 e6040f8..01f54ad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13449,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/contextualsearch/OWNERS b/core/java/android/app/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/core/java/android/app/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e9b94c9..87fb843 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -185,7 +185,7 @@
*
* @param context A Context object which should be some mock instance (like the
* instance of {@link android.test.mock.MockContext}).
- * @param readPermission The read permision you want this instance should have in the
+ * @param readPermission The read permission you want this instance should have in the
* test, which is available via {@link #getReadPermission()}.
* @param writePermission The write permission you want this instance should have
* in the test, which is available via {@link #getWritePermission()}.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9253998..a126363 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -387,7 +387,7 @@
* {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
* <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
* the arguments {@link Bundle}, the Content framework will attempt to
- * synthesize an QUERY_ARG_SQL* argument using the corresponding
+ * synthesize a QUERY_ARG_SQL* argument using the corresponding
* QUERY_ARG_SORT* values.
*/
public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8744eae..bc29f8b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4479,6 +4479,10 @@
* the Android Keystore backed by an isolated execution environment. The version indicates
* which features are implemented in the isolated execution environment:
* <ul>
+ * <li>300: Ability to include a second IMEI in the ID attestation record, see
+ * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+ * <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
+ * X25519 key agreement).
* <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
* for app-generated attestation keys (see {@link
* android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
@@ -4508,6 +4512,11 @@
* StrongBox</a>. If this feature has a version, the version number indicates which features are
* implemented in StrongBox:
* <ul>
+ * <li>300: Ability to include a second IMEI in the ID attestation record, see
+ * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+ * <li>200: No new features for StrongBox (the Android Keystore environment backed by an
+ * isolated execution environment has gained support for Curve 25519 in this version, but
+ * the implementation backed by a dedicated secure processor is not expected to implement it).
* <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
* for app-generated attestation keys (see {@link
* android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index c3a09ae..e8d5d37 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -20,6 +20,7 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -49,7 +50,7 @@
public static Intent createCredentialSelectorIntent(
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
+ @Nullable
ArrayList<ProviderData> enabledProviderDataList,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
@@ -57,23 +58,30 @@
@NonNull ResultReceiver resultReceiver,
boolean isRequestForAllOptions) {
- Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
- disabledProviderDataList, resultReceiver);
+ Intent intent;
+ if (enabledProviderDataList != null) {
+ intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+ disabledProviderDataList, resultReceiver);
+ } else {
+ intent = createCredentialSelectorIntent(requestInfo,
+ disabledProviderDataList, resultReceiver);
+ }
intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
return intent;
}
- /** Generate a new launch intent to the Credential Selector UI. */
+ /**
+ * Generate a new launch intent to the Credential Selector UI.
+ *
+ * @hide
+ */
@NonNull
public static Intent createCredentialSelectorIntent(
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
- ArrayList<ProviderData> enabledProviderDataList,
- @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @NonNull
- ArrayList<DisabledProviderData> disabledProviderDataList,
+ @NonNull
+ ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
Intent intent = new Intent();
ComponentName componentName =
@@ -83,9 +91,6 @@
com.android.internal.R.string
.config_credentialManagerDialogComponent));
intent.setComponent(componentName);
-
- intent.putParcelableArrayListExtra(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -95,6 +100,24 @@
return intent;
}
+ /** Generate a new launch intent to the Credential Selector UI. */
+ @NonNull
+ public static Intent createCredentialSelectorIntent(
+ @NonNull RequestInfo requestInfo,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
+ ArrayList<ProviderData> enabledProviderDataList,
+ @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+ @NonNull
+ ArrayList<DisabledProviderData> disabledProviderDataList,
+ @NonNull ResultReceiver resultReceiver) {
+ Intent intent = createCredentialSelectorIntent(requestInfo,
+ disabledProviderDataList, resultReceiver);
+ intent.putParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+ return intent;
+ }
+
/**
* Creates an Intent that cancels any UI matching the given request token id.
*
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index cb1d3f5..3b7ade2 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -511,7 +511,7 @@
Bundle getExtras();
/**
- * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
+ * This is an out-of-band way for the user of a cursor to communicate with the cursor. The
* structure of each bundle is entirely defined by the cursor.
*
* <p>One use of this is to tell a cursor that it should retry its network request after it
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/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f3496e7..b1ef05a 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -71,7 +71,7 @@
*/
public class ZygoteProcess {
- private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+ private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 60000;
/**
* Use a relatively short delay, because for app zygote, this is in the critical path of
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
new file mode 100644
index 0000000..463adf4
--- /dev/null
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -0,0 +1,3 @@
+srazdan@google.com
+volnov@google.com
+hackz@google.com
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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1fdd1a5..f54ef38 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -425,12 +425,12 @@
int mMotionViewNewTop;
/**
- * The X value associated with the the down motion event
+ * The X value associated with the down motion event
*/
int mMotionX;
/**
- * The Y value associated with the the down motion event
+ * The Y value associated with the down motion event
*/
@UnsupportedAppUsage
int mMotionY;
@@ -7381,7 +7381,7 @@
scrap.dispatchStartTemporaryDetach();
- // The the accessibility state of the view may change while temporary
+ // the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
@@ -7750,7 +7750,7 @@
}
/**
- * Abstract positon scroller used to handle smooth scrolling.
+ * Abstract position scroller used to handle smooth scrolling.
*/
static abstract class AbsPositionScroller {
public abstract void start(int position);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 76e97ad..3b7e1e9 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -170,7 +170,7 @@
* @see android.view.View#measure(int, int)
*
* Figure out the dimensions of this Spinner. The width comes from
- * the widthMeasureSpec as Spinnners can't have their width set to
+ * the widthMeasureSpec as Spinners can't have their width set to
* UNSPECIFIED. The height is based on the height of the selected item
* plus padding.
*/
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/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index f234637..14fb17c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -84,6 +84,14 @@
}
flag {
+ name: "delegate_unhandled_drags"
+ namespace: "multitasking"
+ description: "Enables delegating unhandled drags to SystemUI"
+ bug: "320797628"
+ is_fixed_read_only: true
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4a6e8d7..2e20cce 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -16,7 +16,7 @@
namespace: "windowing_sdk"
name: "activity_embedding_overlay_presentation_flag"
description: "Whether the overlay presentation feature is enabled"
- bug: "243518738"
+ bug: "293370683"
}
flag {
@@ -30,7 +30,7 @@
namespace: "windowing_sdk"
name: "fullscreen_dim_flag"
description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
- bug: "253533308"
+ bug: "293797706"
}
flag {
@@ -44,7 +44,7 @@
namespace: "windowing_sdk"
name: "untrusted_embedding_any_app_permission"
description: "Feature flag to enable the permission to embed any app in untrusted mode."
- bug: "289199433"
+ bug: "293647332"
is_fixed_read_only: true
}
@@ -60,5 +60,5 @@
namespace: "windowing_sdk"
name: "embedded_activity_back_nav_flag"
description: "Refines embedded activity back navigation behavior"
- bug: "240575809"
+ bug: "293642394"
}
\ No newline at end of file
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 21d2bf2..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2645,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 -->
<!-- ======================================== -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ec47313..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>
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/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index bd2f36f..d57f1fc 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -658,6 +658,52 @@
Truth.assertThat(matchingPackets).hasSize(1);
}
+ @Test
+ public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException {
+ final int singleIntValue = 101;
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> sTestDataSource.trace(ctx -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, singleIntValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+
+ ctx.flush();
+ }),
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == singleIntValue).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
interface RunnableCreator {
Runnable create(int state, AtomicInteger stateOut);
}
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 d4c2c2b..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">
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 d0db708..a43a951 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
@@ -259,7 +259,7 @@
/** One handed mode controller to register transition listener. */
private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -285,7 +285,7 @@
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -463,7 +463,7 @@
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
- mDragAndDropController.ifPresent(controller -> controller.addListener(this::collapseStack));
+ mDragAndDropController.addListener(this::collapseStack);
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index b52a118..d4ed017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -83,7 +83,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -94,8 +93,8 @@
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
- displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
- mainHandler, systemWindows);
+ displayImeController, displayInsetsController, transitions, transactionPool,
+ iconProvider, recentTasks, launchAdjacentController, mainExecutor, mainHandler,
+ systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index fc97c798..0d6a852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -78,7 +78,6 @@
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -203,20 +202,6 @@
@WMSingleton
@Provides
- static Optional<DragAndDropController> provideDragAndDropController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
- shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
- }
-
- @WMSingleton
- @Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
Context context,
ShellInit shellInit,
@@ -911,7 +896,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropControllerOptional,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 36f06e8..ead5ad2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -172,7 +172,7 @@
BubblePositioner positioner,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -338,7 +338,7 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -553,6 +553,24 @@
}
//
+ // Drag and drop
+ //
+
+ @WMSingleton
+ @Provides
+ static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DragAndDropController(context, shellInit, shellController,
+ shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+ }
+
+ //
// Misc
//
@@ -562,6 +580,7 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
+ DragAndDropController dragAndDropController,
DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index fdfb6f3..269c369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -105,25 +105,7 @@
void onDragStarted();
}
- /**
- * Creates {@link DragAndDropController}. Returns {@code null} if the feature is disabled.
- */
- public static DragAndDropController create(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- ShellExecutor mainExecutor) {
- if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
- return null;
- }
- return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, mainExecutor);
- }
-
- DragAndDropController(Context context,
+ public DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 70cb2fc..1b124c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -184,7 +184,7 @@
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
@@ -214,7 +214,7 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -290,7 +290,7 @@
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = Optional.of(dragAndDropController);
+ mDragAndDropController = dragAndDropController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
@@ -328,7 +328,9 @@
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
}
- mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+ if (mDragAndDropController != null) {
+ mDragAndDropController.setSplitScreenController(this);
+ }
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index c101425..aec4d11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -74,7 +74,6 @@
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -85,7 +84,7 @@
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
+ displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
Optional.empty(), mainExecutor);
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/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/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7b6dc38..6b0620c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -44,6 +45,7 @@
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
import android.net.Uri;
import android.net.http.SslCertificate;
@@ -959,12 +961,12 @@
/**
* Called when the TV App sends the selected track info as a response to
- * requestSelectedTrackInfo.
+ * {@link #requestSelectedTrackInfo()}
*
- * @param tracks
- * @hide
+ * @param tracks A list of {@link TvTrackInfo} that are currently selected
*/
- public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
}
@Override
@@ -1373,13 +1375,13 @@
}
/**
- * Requests the currently selected {@link TvTrackInfo} from the TV App.
+ * Requests a list of the currently selected {@link TvTrackInfo} from the TV App.
*
* <p> Normally, track info cannot be synchronized until the channel has
- * been changed. This is used when the session of the TIAS is newly
- * created and the normal synchronization has not happened yet.
- * @hide
+ * been changed. This is used when the session of the {@link TvInteractiveAppService}
+ * is newly created and the normal synchronization has not happened yet.
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
@CallSuper
public void requestSelectedTrackInfo() {
executeOrPostRunnableOnMainThread(() -> {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 3b29574..584ea84 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -30,6 +31,7 @@
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.TvInteractiveAppManager.Session;
import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
@@ -585,8 +587,9 @@
/**
* Sends the currently selected track info to the TV Interactive App.
*
- * @hide
+ * @param tracks list of {@link TvTrackInfo} of the currently selected track(s)
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
if (DEBUG) {
Log.d(TAG, "sendSelectedTrackInfo");
@@ -1248,8 +1251,8 @@
* called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 70202463..c18a2de 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -41,7 +41,7 @@
public EffectsTest() {
- Log.d(TAG, "contructor");
+ Log.d(TAG, "constructor");
}
@Override
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/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 6cafcf7..bd9d2e6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -32,6 +32,7 @@
import android.os.Bundle
import android.os.ResultReceiver
import android.util.Log
+import android.view.autofill.AutofillManager
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
@@ -80,9 +81,10 @@
CreateCredentialProviderData::class.java
) ?: emptyList()
RequestInfo.TYPE_GET ->
- intent.extras?.getParcelableArrayList(
- ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
- GetCredentialProviderData::class.java
+ getEnabledProviderDataList(
+ intent
+ ) ?: getEnabledProviderDataListFromAuthExtras(
+ intent
) ?: emptyList()
else -> {
Log.d(
@@ -238,6 +240,24 @@
)
}
+ private fun getEnabledProviderDataList(intent: Intent): List<GetCredentialProviderData>? {
+ return intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData::class.java
+ )
+ }
+
+ private fun getEnabledProviderDataListFromAuthExtras(
+ intent: Intent
+ ): List<GetCredentialProviderData>? {
+ return intent.getBundleExtra(
+ AutofillManager.EXTRA_AUTH_STATE
+ ) ?.getParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData::class.java
+ )
+ }
+
// IMPORTANT: new invocation should be mindful that this method can throw.
private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
return CreateFlowUtils.toEnabledProviderList(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1f1d236..07f1fa3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,22 +16,24 @@
package com.android.credentialmanager.autofill
-import android.R
+import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
-import android.app.PendingIntent
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialRequest
-import android.credentials.GetCandidateCredentialsResponse
-import android.credentials.GetCandidateCredentialsException
+import android.credentials.Credential
+import android.credentials.CredentialManager
import android.credentials.CredentialOption
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCredentialRequest
+import android.credentials.GetCredentialResponse
+import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.ProviderData
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import android.provider.Settings
-import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -44,12 +46,11 @@
import android.service.autofill.SaveRequest
import android.service.credentials.CredentialProviderService
import android.util.Log
+import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.view.autofill.IAutoFillManagerClient
-import android.view.autofill.AutofillId
-import android.widget.inline.InlinePresentationSpec
-import android.credentials.CredentialManager
import android.widget.RemoteViews
+import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
@@ -61,10 +62,9 @@
import com.android.credentialmanager.ktx.credentialEntry
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.get.ProviderInfo
+import java.util.concurrent.Executors
import org.json.JSONException
import org.json.JSONObject
-import java.util.concurrent.Executors
class CredentialAutofillService : AutofillService() {
@@ -153,8 +153,7 @@
return
}
- val fillResponse = convertToFillResponse(result, request,
- this@CredentialAutofillService)
+ val fillResponse = convertToFillResponse(result, request)
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -231,7 +230,7 @@
}
private fun getEntryToIconMap(
- candidateProviderDataList: MutableList<GetCredentialProviderData>
+ candidateProviderDataList: List<GetCredentialProviderData>
): Map<String, Icon> {
val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
candidateProviderDataList.forEach { provider ->
@@ -261,20 +260,16 @@
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest,
- context: Context
+ filLRequest: FillRequest
): FillResponse? {
- val providerList = GetFlowUtils.toProviderList(
- getCredResponse.candidateProviderDataList,
- context)
- if (providerList.isEmpty()) {
+ val candidateProviders = getCredResponse.candidateProviderDataList
+ if (candidateProviders.isEmpty()) {
return null
}
- val entryIconMap: Map<String, Icon> =
- getEntryToIconMap(getCredResponse.candidateProviderDataList)
- val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
- mapAutofillIdToProviders(providerList)
+ val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
+ val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
+ mapAutofillIdToProviders(candidateProviders)
val fillResponseBuilder = FillResponse.Builder()
var validFillResponse = false
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
@@ -292,11 +287,14 @@
private fun processProvidersForAutofillId(
filLRequest: FillRequest,
autofillId: AutofillId,
- providerList: List<ProviderInfo>,
+ providerDataList: ArrayList<GetCredentialProviderData>,
entryIconMap: Map<String, Icon>,
fillResponseBuilder: FillResponse.Builder,
bottomSheetPendingIntent: PendingIntent?
): Boolean {
+ val providerList = GetFlowUtils.toProviderList(
+ providerDataList,
+ this@CredentialAutofillService)
if (providerList.isEmpty()) {
return false
}
@@ -340,7 +338,7 @@
return@usernameLoop
}
if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
- return@usernameLoop;
+ return@usernameLoop
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -398,19 +396,20 @@
inlinePresentationSpecsCount)
if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
- fillResponseBuilder)
+ fillResponseBuilder, providerDataList)
}
return datasetAdded
}
- private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
- pendingIntent: PendingIntent,
- icon: Icon,
- spec: InlinePresentationSpec,
- duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
- InlinePresentation {
- val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
- && primaryEntry.displayName != null) {
+ private fun createInlinePresentation(
+ primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
+ ): InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+ primaryEntry.displayName != null) {
primaryEntry.displayName!!
} else {
primaryEntry.userName
@@ -430,7 +429,8 @@
private fun addDropdownMoreOptionsPresentation(
bottomSheetPendingIntent: PendingIntent,
autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder) {
+ fillResponseBuilder: FillResponse.Builder
+ ) {
val presentationBuilder = Presentations.Builder()
.setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
@@ -460,7 +460,8 @@
bottomSheetPendingIntent: PendingIntent,
spec: InlinePresentationSpec,
autofillId: AutofillId,
- fillResponseBuilder: FillResponse.Builder
+ fillResponseBuilder: FillResponse.Builder,
+ providerDataList: ArrayList<GetCredentialProviderData>
) {
val dataSetBuilder = Dataset.Builder()
val sliceBuilder = InlineSuggestionUi
@@ -471,6 +472,10 @@
.setInlinePresentation(InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ true))
+ val extraBundle = Bundle()
+ extraBundle.putParcelableArrayList(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
+
fillResponseBuilder.addDataset(
dataSetBuilder
.setField(
@@ -479,6 +484,7 @@
presentationBuilder.build())
.build())
.setAuthentication(bottomSheetPendingIntent.intentSender)
+ .setAuthenticationExtras(extraBundle)
.build()
)
}
@@ -514,16 +520,16 @@
* }
*/
private fun mapAutofillIdToProviders(
- providerList: List<ProviderInfo>
- ): Map<AutofillId, List<ProviderInfo>> {
- val autofillIdToProviders: MutableMap<AutofillId, MutableList<ProviderInfo>> =
- mutableMapOf()
+ providerList: List<GetCredentialProviderData>
+ ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
+ val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
+ mutableMapOf()
providerList.forEach { provider ->
val autofillIdToCredentialEntries:
- MutableMap<AutofillId, MutableList<CredentialEntryInfo>> =
- mapAutofillIdToCredentialEntries(provider.credentialEntryList)
+ MutableMap<AutofillId, ArrayList<Entry>> =
+ mapAutofillIdToCredentialEntries(provider.credentialEntries)
autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
- autofillIdToProviders.getOrPut(autofillId) { mutableListOf() }
+ autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
.add(copyProviderInfo(provider, entries))
}
}
@@ -531,13 +537,13 @@
}
private fun mapAutofillIdToCredentialEntries(
- credentialEntryList: List<CredentialEntryInfo>
- ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
+ credentialEntryList: List<Entry>
+ ): MutableMap<AutofillId, ArrayList<Entry>> {
val autofillIdToCredentialEntries:
- MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
+ MutableMap<AutofillId, ArrayList<Entry>> = mutableMapOf()
credentialEntryList.forEach entryLoop@{ credentialEntry ->
val autofillId: AutofillId? = credentialEntry
- .fillInIntent
+ .frameworkExtrasIntent
?.getParcelableExtra(
CredentialProviderService.EXTRA_AUTOFILL_ID,
AutofillId::class.java)
@@ -546,24 +552,22 @@
" Integration might be disabled.")
return@entryLoop
}
- autofillIdToCredentialEntries.getOrPut(autofillId) { mutableListOf() }
+ autofillIdToCredentialEntries.getOrPut(autofillId) { ArrayList() }
.add(credentialEntry)
}
return autofillIdToCredentialEntries
}
private fun copyProviderInfo(
- providerInfo: ProviderInfo,
- credentialList: List<CredentialEntryInfo>
- ): ProviderInfo {
- return ProviderInfo(
- providerInfo.id,
- providerInfo.icon,
- providerInfo.displayName,
- credentialList,
- providerInfo.authenticationEntryList,
- providerInfo.remoteEntry,
- providerInfo.actionEntryList
+ providerInfo: GetCredentialProviderData,
+ credentialList: List<Entry>
+ ): GetCredentialProviderData {
+ return GetCredentialProviderData(
+ providerInfo.providerFlattenedComponentName,
+ credentialList,
+ providerInfo.actionChips,
+ providerInfo.authenticationEntries,
+ providerInfo.remoteEntry
)
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 32c7433..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -911,6 +911,9 @@
<!-- 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/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/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index dfeb1f3..d821f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,7 +33,7 @@
public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
protected View mEcaView;
- // To avoid accidental lockout due to events while the device in in the pocket, ignore
+ // To avoid accidental lockout due to events while the device in the pocket, ignore
// any passwords with length less than or equal to this length.
protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
private KeyDownListener mKeyDownListener;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index bc12aee..ce03072 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -132,7 +132,7 @@
boolean shouldSubtleWindowAnimationsForUnlock();
/**
- * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+ * Starts the animation before we dismiss Keyguard, i.e. a disappearing animation on the
* security view of the bouncer.
*
* @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
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/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/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/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 9b83b75..ee3706a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -80,7 +80,7 @@
}
/**
- * Click listener for messsage.
+ * Click listener for message.
*/
public @Nullable View.OnClickListener getClickListener() {
return mOnClickListener;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f085e88..4766a84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -444,7 +444,7 @@
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
* called.
- * */
+ */
private boolean mHiding;
/**
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/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/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 0831971..25a7eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,12 +16,14 @@
package com.android.systemui.shade
+import android.content.Context
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.view.WindowManager
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,18 +35,21 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Assert.assertThrows
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -52,12 +57,17 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@Ignore("b/323053208")
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class GlanceableHubContainerControllerTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos: Kosmos =
+ testKosmos().apply {
+ // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
+ // SharedFlow
+ testDispatcher = UnconfinedTestDispatcher()
+ }
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -104,17 +114,19 @@
R.dimen.communal_bottom_edge_swipe_region_height,
BOTTOM_SWIPE_REGION_WIDTH
)
+
+ initAndAttachContainerView()
}
@Test
- fun isEnabled_interactorEnabled_interceptsTouches() {
+ fun isEnabled_communalEnabled_returnsTrue() {
communalRepository.setIsCommunalEnabled(true)
assertThat(underTest.isEnabled()).isTrue()
}
@Test
- fun isEnabled_interactorDisabled_doesNotIntercept() {
+ fun isEnabled_communalDisabled_returnsFalse() {
communalRepository.setIsCommunalEnabled(false)
assertThat(underTest.isEnabled()).isFalse()
@@ -124,11 +136,29 @@
fun initView_notEnabled_throwsException() {
communalRepository.setIsCommunalEnabled(false)
+ underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ )
+
assertThrows(RuntimeException::class.java) { underTest.initView(context) }
}
@Test
fun initView_calledTwice_throwsException() {
+ underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ keyguardTransitionInteractor,
+ shadeInteractor,
+ powerManager,
+ )
+
// First call succeeds.
underTest.initView(context)
@@ -137,25 +167,20 @@
}
@Test
- fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
- // Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ fun onTouchEvent_communalClosed_doesNotIntercept() {
+ // Communal is closed.
+ goToScene(CommunalSceneKey.Blank)
- initAndAttachContainerView()
-
- // Touch events are intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
@Test
- fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
- // Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ fun onTouchEvent_openGesture_interceptsTouches() {
+ // Communal is closed.
+ goToScene(CommunalSceneKey.Blank)
- initAndAttachContainerView()
-
- // Initial touch down is intercepted, and so are touches outside of the region, until an up
- // event is received.
+ // Initial touch down is intercepted, and so are touches outside of the region, until an
+ // up event is received.
assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
@@ -165,34 +190,27 @@
@Test
fun onTouchEvent_communalOpen_interceptsTouches() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+ goToScene(CommunalSceneKey.Communal)
- initAndAttachContainerView()
- testableLooper.processAllMessages()
-
- // Touch events are intercepted.
+ // Touch events are intercepted outside of any gesture areas.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
// User activity sent to PowerManager.
verify(powerManager).userActivity(any(), any(), any())
}
@Test
- fun onTouchEvent_topSwipeWhenHubOpen_returnsFalse() {
+ fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Touch event in the top swipe reqgion is not intercepted.
assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
}
@Test
- fun onTouchEvent_bottomSwipeWhenHubOpen_returnsFalse() {
+ fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Touch event in the bottom swipe reqgion is not intercepted.
assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
@@ -201,9 +219,7 @@
@Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
// Bouncer is visible.
bouncerShowingFlow.value = true
@@ -218,9 +234,7 @@
@Test
fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
+ goToScene(CommunalSceneKey.Communal)
shadeShowingFlow.value = true
testableLooper.processAllMessages()
@@ -232,10 +246,7 @@
@Test
fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
// Communal is open.
- communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
- initAndAttachContainerView()
- testableLooper.processAllMessages()
+ goToScene(CommunalSceneKey.Communal)
// Touch events are intercepted.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
@@ -253,15 +264,24 @@
parentView = FrameLayout(context)
parentView.addView(containerView)
- // Make view clickable so that dispatchTouchEvent returns true.
- containerView.isClickable = true
-
underTest.initView(containerView)
+
// Attach the view so that flows start collecting.
ViewUtils.attachView(parentView)
- // Give the view a size so that determining if a touch starts at the right edge works.
- parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
- containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
+
+ // Give the view a fixed size to simplify testing for edge swipes.
+ val lp =
+ parentView.layoutParams.apply {
+ width = CONTAINER_WIDTH
+ height = CONTAINER_HEIGHT
+ }
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ wm.updateViewLayout(parentView, lp)
+ }
+
+ private fun goToScene(scene: CommunalSceneKey) {
+ communalRepository.setDesiredScene(scene)
+ testableLooper.processAllMessages()
}
companion object {
@@ -271,13 +291,17 @@
private const val TOP_SWIPE_REGION_WIDTH = 20
private const val BOTTOM_SWIPE_REGION_WIDTH = 20
+ /**
+ * A touch down event right in the middle of the screen, to avoid being in any of the swipe
+ * regions.
+ */
private val DOWN_EVENT =
MotionEvent.obtain(
0L,
0L,
MotionEvent.ACTION_DOWN,
- CONTAINER_WIDTH.toFloat(),
- CONTAINER_HEIGHT.toFloat(),
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
0
)
private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
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/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9ea4142..f3e9203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -616,7 +616,7 @@
mPositioner,
mock(DisplayController.class),
mOneHandedOptional,
- Optional.of(mock(DragAndDropController.class)),
+ mock(DragAndDropController.class),
syncExecutor,
mock(Handler.class),
mTaskViewTransitions,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 9ad234e1..4a5ebd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -71,7 +71,7 @@
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
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..aafaaff 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -21,7 +21,7 @@
classes that have partial success.
Typical usage:
-$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ RAVENWOOD_RUN_DISABLED_TESTS=1 atest MyTestsRavenwood
$ cd /path/to/tests/root
$ python bulk_enable.py /path/to/atest/output/host_log.txt
"""
@@ -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/coretest/Android.bp b/ravenwood/coretest/Android.bp
new file mode 100644
index 0000000..9b7f8f7
--- /dev/null
+++ b/ravenwood/coretest/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodCoreTest",
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ ],
+
+ srcs: [
+ "test/**/*.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
new file mode 100644
index 0000000..e58c282
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.coretest;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+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.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for the test runner validator in RavenwoodRule.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestRunnerValidationTest {
+ // Note the following rules don't have a @Rule, because they need to be applied in a specific
+ // order. So we use a RuleChain instead.
+ private ExpectedException mThrown = ExpectedException.none();
+ private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Rule
+ public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+ public RavenwoodTestRunnerValidationTest() {
+ Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+ // Because RavenwoodRule will throw this error before executing the test method,
+ // we can't do it in the test method itself.
+ // So instead, we initialize it here.
+ mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
+ }
+
+ @Test
+ public void testValidateTestRunner() {
+ }
+}
diff --git a/ravenwood/fix_test_runner.py b/ravenwood/fix_test_runner.py
new file mode 100755
index 0000000..99b7a1f
--- /dev/null
+++ b/ravenwood/fix_test_runner.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+"""
+Tool switch the deprecated jetpack test runner to the correct one.
+
+Typical usage:
+$ RAVENWOOD_OPTIONAL_VALIDATION=1 atest MyTestsRavenwood # Prepend RAVENWOOD_RUN_DISABLED_TESTS=1 as needed
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+OLD_RUNNER = "androidx.test.runner.AndroidJUnit4"
+NEW_RUNNER = "androidx.test.ext.junit.runners.AndroidJUnit4"
+SED_ARG = r"s/%s/%s/g" % (OLD_RUNNER, NEW_RUNNER)
+
+target = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+ for line in f.readlines():
+ result = re_result.search(line)
+ if result:
+ clazz, method, state, msg = result.groups()
+ if NEW_RUNNER in msg:
+ target[clazz] = 1
+
+if len(target) == 0:
+ print("No tests need updating.")
+ sys.exit(0)
+
+num_fixed = 0
+for clazz in target.keys():
+ print("Fixing test runner", clazz)
+ clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+ found = False
+ for root, dirs, files in os.walk("."):
+ for f in files:
+ if clazz_match.match(f):
+ found = True
+ num_fixed += 1
+ path = os.path.join(root, f)
+ subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+ if not found:
+ print(f" Warining: tests {clazz} not found")
+
+
+print("Tests fixed", num_fixed)
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..85fa65a 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;
@@ -26,14 +28,19 @@
import com.android.internal.os.RuntimeInit;
+import org.junit.Assert;
import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
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 +57,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 +87,8 @@
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
+
com.android.server.LocalServices.removeAllServicesForTest();
if (rule.mProvideMainThread) {
@@ -78,6 +103,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 +123,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 +153,48 @@
}
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);
+ }
+ }
+ }
+
+ public static void validate(Statement base, Description description,
+ boolean enableOptionalValidation) {
+ validateTestRunner(base, description, enableOptionalValidation);
+ }
+
+ private static void validateTestRunner(Statement base, Description description,
+ boolean shouldFail) {
+ final var testClass = description.getTestClass();
+ final var runWith = testClass.getAnnotation(RunWith.class);
+ if (runWith == null) {
+ return;
+ }
+
+ // Due to build dependencies, we can't directly refer to androidx classes here,
+ // so just check the class name instead.
+ if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
+ var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
+ + " test runner androidx.test.runner.AndroidJUnit4."
+ + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
+ if (shouldFail) {
+ Assert.fail(message);
+ } else {
+ System.err.println("Warning: " + message);
+ }
+ }
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b38..764573d 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;
@@ -85,6 +89,12 @@
private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
!REALLY_DISABLE_PATTERN.pattern().isEmpty();
+ /**
+ * If true, enable optional validation on running tests.
+ */
+ private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
+ System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
+
static {
if (ENABLE_PROBE_IGNORED) {
System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
@@ -94,12 +104,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.
@@ -271,6 +281,12 @@
}
}
+ private void commonPrologue(Statement base, Description description) {
+ RavenwoodRuleImpl.logTestRunner("started", description);
+ RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
+ RavenwoodRuleImpl.init(RavenwoodRule.this);
+ }
+
/**
* Run the given {@link Statement} with no special treatment.
*/
@@ -280,8 +296,7 @@
public void evaluate() throws Throwable {
Assume.assumeTrue(shouldEnableOnRavenwood(description));
- RavenwoodRuleImpl.logTestRunner("started", description);
- RavenwoodRuleImpl.init(RavenwoodRule.this);
+ commonPrologue(base, description);
try {
base.evaluate();
RavenwoodRuleImpl.logTestRunner("finished", description);
@@ -306,8 +321,7 @@
public void evaluate() throws Throwable {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- RavenwoodRuleImpl.logTestRunner("started", description);
- RavenwoodRuleImpl.init(RavenwoodRule.this);
+ commonPrologue(base, description);
try {
base.evaluate();
} catch (Throwable t) {
@@ -327,4 +341,11 @@
}
};
}
+
+ /**
+ * Do not use it outside ravenwood core classes.
+ */
+ public boolean _ravenwood_private$isOptionalValidationEnabled() {
+ return ENABLE_OPTIONAL_VALIDATION;
+ }
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 7d172f2..e951351b 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,6 +17,7 @@
package android.platform.test.ravenwood;
import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
public class RavenwoodRuleImpl {
public static boolean isOnRavenwood() {
@@ -34,4 +35,8 @@
public static void logTestRunner(String label, Description description) {
// No-op when running on a real device
}
+
+ public static void validate(Statement base, Description description,
+ boolean enableOptionalValidation) {
+ }
}
diff --git a/ravenwood/minimum-test/Android.bp b/ravenwood/minimum-test/Android.bp
index bf3583c..e4ed3d5 100644
--- a/ravenwood/minimum-test/Android.bp
+++ b/ravenwood/minimum-test/Android.bp
@@ -13,6 +13,7 @@
static_libs: [
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
],
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 7abfecf..03cfad5 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -18,7 +18,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Rule;
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/contextualsearch/OWNERS b/services/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/services/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
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/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 2ea662c..fff4939 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,24 +3185,6 @@
}
}
}
-
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- String ime = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
- String defaultDeviceIme = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- if (DEBUG) {
- Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
- + " device input method for user " + mSettings.getUserId()
- + " - restoring " + defaultDeviceIme);
- }
- SecureSettingsWrapper.putString(
- Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
- mSettings.getUserId());
- }
- }
-
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -3726,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,
@@ -3774,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
@@ -5384,11 +5370,7 @@
if (!setSubtypeOnly) {
// Set InputMethod here
- final String imeId = imi != null ? imi.getId() : "";
- mSettings.putSelectedInputMethod(imeId);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- mSettings.putSelectedDefaultDeviceInputMethod(imeId);
- }
+ mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
@@ -5531,9 +5513,6 @@
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- settings.putSelectedDefaultDeviceInputMethod(imeId);
- }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6580,9 +6559,6 @@
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
- if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
- settings.putSelectedDefaultDeviceInputMethod(nextIme);
- }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index e444db1..a51002b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,6 +36,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -87,6 +88,12 @@
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
+ String ime = getSelectedInputMethod();
+ String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ putSelectedInputMethod(defaultDeviceIme);
+ putSelectedDefaultDeviceInputMethod(null);
+ }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c9bbab..31c1d76 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8844,19 +8844,26 @@
}
}
+ private PendingIntent getNotificationTimeoutPendingIntent(NotificationRecord record,
+ int flags) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ return PendingIntent.getBroadcast(getContext(),
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION_NOTIFICATION_TIMEOUT)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+ .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+ .appendPath(record.getKey()).build())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_KEY, record.getKey()),
+ flags);
+ }
+
@VisibleForTesting
@GuardedBy("mNotificationLock")
void scheduleTimeoutLocked(NotificationRecord record) {
if (record.getNotification().getTimeoutAfter() > 0) {
- final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
- REQUEST_CODE_TIMEOUT,
- new Intent(ACTION_NOTIFICATION_TIMEOUT)
- .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
- .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
- .appendPath(record.getKey()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_KEY, record.getKey()),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ final PendingIntent pi = getNotificationTimeoutPendingIntent(
+ record, PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
}
@@ -8864,6 +8871,16 @@
@VisibleForTesting
@GuardedBy("mNotificationLock")
+ void cancelScheduledTimeoutLocked(NotificationRecord record) {
+ final PendingIntent pi = getNotificationTimeoutPendingIntent(
+ record, PendingIntent.FLAG_CANCEL_CURRENT);
+ if (pi != null) {
+ mAlarmManager.cancel(pi);
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mNotificationLock")
/**
* Determine whether this notification should attempt to make noise, vibrate, or flash the LED
* @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
@@ -9894,21 +9911,7 @@
int rank, int count, boolean wasPosted, String listenerName,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final String canceledKey = r.getKey();
-
- // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
- // does not already exist, then null will be returned.
- final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
- REQUEST_CODE_TIMEOUT,
- new Intent(ACTION_NOTIFICATION_TIMEOUT)
- .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
- .appendPath(r.getKey()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
-
- // Cancel alarm corresponding to pi.
- if (pi != null) {
- mAlarmManager.cancel(pi);
- }
+ cancelScheduledTimeoutLocked(r);
// Record caller.
recordCallerLocked(r);
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/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/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 4203576..e8b32cd 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -178,7 +178,7 @@
// intents
return PendingIntent.getActivityAsUser(
mContext, /*requestCode=*/0, intent,
- PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
+ PendingIntent.FLAG_MUTABLE, /*options=*/null,
UserHandle.of(mUserId));
}
}
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 7e709fe..1a9a0e6 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -116,7 +116,7 @@
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
- providerDataList,
+ /*providerDataList=*/ null,
/*isRequestForAllOptions=*/ true);
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index fcaef9f..ca23d62 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,6 +30,7 @@
import android.credentials.selection.Entry;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderPendingIntentResponse;
+import android.os.Bundle;
import android.os.ICancellationSignal;
import android.service.autofill.Flags;
import android.service.credentials.Action;
@@ -76,6 +77,9 @@
@NonNull
private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
+ @NonNull
+ private final Map<String, AutofillId> mCredentialEntryKeyToAutofilLIdMap;
+
/** The complete request to be used in the second round. */
private final android.credentials.GetCredentialRequest mCompleteRequest;
@@ -245,6 +249,7 @@
mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
mProviderResponseDataHandler = new ProviderResponseDataHandler(
ComponentName.unflattenFromString(hybridService));
+ mCredentialEntryKeyToAutofilLIdMap = new HashMap<>();
}
/** Called when the provider response has been updated by an external source. */
@@ -298,7 +303,7 @@
invokeCallbackOnInternalInvalidState();
return;
}
- onCredentialEntrySelected(providerPendingIntentResponse);
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
break;
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
@@ -307,7 +312,7 @@
invokeCallbackOnInternalInvalidState();
return;
}
- onActionEntrySelected(providerPendingIntentResponse);
+ onActionEntrySelected(providerPendingIntentResponse, entryKey);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
Action authenticationEntry = mProviderResponseDataHandler
@@ -337,7 +342,7 @@
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
- onRemoteEntrySelected(providerPendingIntentResponse);
+ onRemoteEntrySelected(providerPendingIntentResponse, entryKey);
} else {
Slog.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
@@ -376,7 +381,7 @@
return null;
}
- private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id, String entryKey) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
@@ -392,6 +397,7 @@
.getParcelable(CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
if (autofillId != null && Flags.autofillCredmanIntegration()) {
intent.putExtra(CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId);
+ mCredentialEntryKeyToAutofilLIdMap.put(entryKey, autofillId);
}
return intent.putExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -408,12 +414,13 @@
}
private void onRemoteEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse) {
- onCredentialEntrySelected(providerPendingIntentResponse);
+ ProviderPendingIntentResponse providerPendingIntentResponse, String entryKey) {
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
}
private void onCredentialEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse) {
+ ProviderPendingIntentResponse providerPendingIntentResponse,
+ String entryKey) {
if (providerPendingIntentResponse == null) {
invokeCallbackOnInternalInvalidState();
return;
@@ -430,7 +437,18 @@
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null) {
+ if (getCredentialResponse != null && getCredentialResponse.getCredential() != null) {
+ Bundle credentialData = getCredentialResponse.getCredential().getData();
+ AutofillId autofillId = mCredentialEntryKeyToAutofilLIdMap.get(entryKey);
+ if (Flags.autofillCredmanIntegration()
+ && entryKey != null && autofillId != null && credentialData != null
+ ) {
+ Slog.d(TAG, "Adding autofillId to credential response: " + autofillId);
+ credentialData.putParcelable(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ mCredentialEntryKeyToAutofilLIdMap.get(entryKey)
+ );
+ }
mCallbacks.onFinalResponseReceived(mComponentName,
getCredentialResponse);
return;
@@ -514,9 +532,9 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
- providerPendingIntentResponse) {
+ providerPendingIntentResponse, String entryKey) {
Slog.i(TAG, "onActionEntrySelected");
- onCredentialEntrySelected(providerPendingIntentResponse);
+ onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
}
@@ -614,7 +632,7 @@
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
setUpFillInIntentWithFinalRequest(credentialEntry
- .getBeginGetCredentialOptionId()));
+ .getBeginGetCredentialOptionId(), id));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
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/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/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef0ac33..df314c2 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1254,6 +1254,11 @@
verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
captor.getValue().getIntent().getPackage());
+
+ mService.cancelScheduledTimeoutLocked(r);
+ verify(mAlarmManager).cancel(captor.capture());
+ assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
+ captor.getValue().getIntent().getPackage());
}
@Test
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;
}
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
index 5d3abc6..9276402 100644
--- a/tools/aapt/ApkBuilder.h
+++ b/tools/aapt/ApkBuilder.h
@@ -44,7 +44,7 @@
android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
/**
- * Adds a file to be written to the final APK. It's name must not collide
+ * Adds a file to be written to the final APK. Its name must not collide
* with that of any files previously added. When a Split APK is being
* generated, duplicates can exist as long as they are in different splits
* (resources.arsc, AndroidManifest.xml).
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 2f2ef92..66a0510 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -187,7 +187,7 @@
" be loaded alongside the base APK at runtime.\n"
" --feature-of\n"
" Builds a split APK that is a feature of the apk specified here. Resources\n"
- " in the base APK can be referenced from the the feature APK.\n"
+ " in the base APK can be referenced from the feature APK.\n"
" --feature-after\n"
" An app can have multiple Feature Split APKs which must be totally ordered.\n"
" If --feature-of is specified, this flag specifies which Feature Split APK\n"