Merge "Show media output header when calling" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f10c0fc..eabe1f1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -872,6 +872,7 @@
public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getAttributionTag();
+ method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @Nullable public String getDeviceId();
method @Nullable public String getPackageName();
method @IntRange(from=0) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 68cc17b..caaaf51 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7438,6 +7438,7 @@
}
mDdmSyncStageUpdater.next(Stage.Running);
+ long timestampApplicationOnCreateNs = 0;
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7480,8 +7481,10 @@
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
+ timestampApplicationOnCreateNs = SystemClock.elapsedRealtimeNanos();
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
+ timestampApplicationOnCreateNs = 0;
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
@@ -7519,7 +7522,7 @@
}
try {
- mgr.finishAttachApplication(mStartSeq);
+ mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 20b2357..2d0f6fc 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3457,6 +3457,8 @@
private @Nullable String mPackageName;
/** Attribution tag of the proxy that noted the op */
private @Nullable String mAttributionTag;
+ /** Persistent device Id of the proxy that noted the op */
+ private @Nullable String mDeviceId;
/**
* Reinit existing object with new state.
@@ -3464,14 +3466,16 @@
* @param uid UID of the proxy app that noted the op
* @param packageName Package of the proxy that noted the op
* @param attributionTag attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
*
* @hide
*/
public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag, @Nullable String deviceId) {
mUid = Preconditions.checkArgumentNonnegative(uid);
mPackageName = packageName;
mAttributionTag = attributionTag;
+ mDeviceId = deviceId;
}
@@ -3505,16 +3509,33 @@
@IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag) {
+ this(uid, packageName, attributionTag,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ /**
+ * Creates a new OpEventProxyInfo.
+ *
+ * @param uid UID of the proxy app that noted the op
+ * @param packageName Package of the proxy that noted the op
+ * @param attributionTag Attribution tag of the proxy that noted the op
+ * @param deviceId Persistent device Id of the proxy that noted the op
+ *
+ * @hide
+ */
+ public OpEventProxyInfo(
+ @IntRange(from = 0) int uid,
+ @Nullable String packageName,
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
- // onConstructed(); // You can define this method to get a callback
+ this.mDeviceId = deviceId;
}
-
/**
* Copy constructor
*
@@ -3525,6 +3546,7 @@
mUid = orig.mUid;
mPackageName = orig.mPackageName;
mAttributionTag = orig.mAttributionTag;
+ mDeviceId = orig.mDeviceId;
}
/**
@@ -3551,6 +3573,9 @@
return mAttributionTag;
}
+ @FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ public @Nullable String getDeviceId() { return mDeviceId; }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -3560,10 +3585,12 @@
byte flg = 0;
if (mPackageName != null) flg |= 0x2;
if (mAttributionTag != null) flg |= 0x4;
+ if (mDeviceId != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUid);
if (mPackageName != null) dest.writeString(mPackageName);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
+ if (mDeviceId != null) dest.writeString(mDeviceId);
}
@Override
@@ -3581,14 +3608,14 @@
int uid = in.readInt();
String packageName = (flg & 0x2) == 0 ? null : in.readString();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-
+ String deviceId = (flg & 0x8) == 0 ? null : in.readString();
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
-
+ this.mDeviceId = deviceId;
// onConstructed(); // You can define this method to get a callback
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3765c81..e8b57f2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -196,7 +196,7 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
- void finishAttachApplication(long startSeq);
+ void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 05a2aec..c3bac71 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,7 +18,6 @@
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
-import static android.app.Flags.updateRankingTime;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -897,15 +896,16 @@
/**
* Sphere of visibility of this notification, which affects how and when the SystemUI reveals
* the notification's presence and contents in untrusted situations (namely, on the secure
- * lockscreen).
+ * lockscreen and during screen sharing).
*
* The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
* done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
* shown in all situations, but the contents are only available if the device is unlocked for
- * the appropriate user.
+ * the appropriate user and there is no active screen sharing session.
*
* A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
- * can be read even in an "insecure" context (that is, above a secure lockscreen).
+ * can be read even in an "insecure" context (that is, above a secure lockscreen or while
+ * screen sharing with a remote viewer).
* To modify the public version of this notification—for example, to redact some portions—see
* {@link Builder#setPublicVersion(Notification)}.
*
@@ -924,7 +924,8 @@
public @interface Visibility {}
/**
- * Notification visibility: Show this notification in its entirety on all lockscreens.
+ * Notification visibility: Show this notification in its entirety on all lockscreens and while
+ * screen sharing.
*
* {@see #visibility}
*/
@@ -932,14 +933,16 @@
/**
* Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
- * private information on secure lockscreens.
+ * private information on secure lockscreens. Conceal sensitive or private information while
+ * screen sharing.
*
* {@see #visibility}
*/
public static final int VISIBILITY_PRIVATE = 0;
/**
- * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
+ * Notification visibility: Do not reveal any part of this notification on a secure lockscreen
+ * or while screen sharing.
*
* {@see #visibility}
*/
@@ -2596,7 +2599,7 @@
public Notification()
{
this.when = System.currentTimeMillis();
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -2612,7 +2615,7 @@
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
@@ -2645,7 +2648,7 @@
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
- if (updateRankingTime()) {
+ if (Flags.sortSectionByTime()) {
creationTime = when;
extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
@@ -3256,8 +3259,9 @@
boolean mustClearCookie = false;
if (!parcel.hasClassCookie(Notification.class)) {
// This is the "root" notification, and not an "inner" notification (including
- // publicVersion or anything else that might be embedded in extras).
- parcel.setClassCookie(Notification.class, this);
+ // publicVersion or anything else that might be embedded in extras). So we want
+ // to use its token for every inner notification (might be null).
+ parcel.setClassCookie(Notification.class, mAllowlistToken);
mustClearCookie = true;
}
try {
@@ -3266,7 +3270,7 @@
writeToParcelImpl(parcel, flags);
} finally {
if (mustClearCookie) {
- parcel.removeClassCookie(Notification.class, this);
+ parcel.removeClassCookie(Notification.class, mAllowlistToken);
}
}
} else {
@@ -3290,14 +3294,9 @@
parcel.writeInt(1);
if (Flags.secureAllowlistToken()) {
- Notification rootNotification = (Notification) parcel.getClassCookie(
- Notification.class);
- if (rootNotification != null && rootNotification != this) {
- // Always use the same token as the root notification
- parcel.writeStrongBinder(rootNotification.mAllowlistToken);
- } else {
- parcel.writeStrongBinder(mAllowlistToken);
- }
+ // Always use the same token as the root notification (might be null).
+ IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
+ parcel.writeStrongBinder(rootNotificationToken);
} else {
parcel.writeStrongBinder(mAllowlistToken);
}
@@ -5981,21 +5980,22 @@
}
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
- contentView.setLong(R.id.chronometer, "setBase",
- mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+ contentView.setLong(R.id.chronometer, "setBase", mN.getWhen()
+ + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
setTextViewColorSecondary(contentView, R.id.chronometer, p);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", mN.when);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen());
setTextViewColorSecondary(contentView, R.id.time, p);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
// on demand in case it's a child notification without anything in the header
- contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+ contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() :
+ mN.creationTime);
setTextViewColorSecondary(contentView, R.id.time, p);
}
}
@@ -7162,7 +7162,7 @@
}
}
- if (!updateRankingTime()) {
+ if (!Flags.sortSectionByTime()) {
mN.creationTime = System.currentTimeMillis();
}
@@ -7615,10 +7615,29 @@
}
/**
+ * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current'
+ * notification. 0 is treated as a special value because it was special in an old version of
+ * android, and some apps are still (incorrectly) using it.
+ *
+ * @hide
+ */
+ public long getWhen() {
+ if (Flags.sortSectionByTime()) {
+ if (when == 0) {
+ return creationTime;
+ }
+ }
+ return when;
+ }
+
+ /**
* @return true if the notification will show the time; false otherwise
* @hide
*/
public boolean showsTime() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_WHEN);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN);
}
@@ -7627,6 +7646,9 @@
* @hide
*/
public boolean showsChronometer() {
+ if (Flags.sortSectionByTime()) {
+ return extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
+ }
return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 9b3fb5c..726064e 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1198,17 +1198,18 @@
* Callback called when a particular foreground service type has timed out.
*
* <p>This callback is meant to give the app a small grace period of a few seconds to finish
- * the foreground service of the offending type - if it fails to do so, the app will be
+ * the foreground service of the associated type - if it fails to do so, the app will be
* declared an ANR.
*
- * <p>The foreground service of the offending type can be stopped within the time limit by
+ * <p>The foreground service of the associated type can be stopped within the time limit by
* {@link android.app.Service#stopSelf()},
* {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
* {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
* service to a "background" service, which will soon be stopped by the system.
*
* <p>The specific time limit for each type (if one exists) is mentioned in the documentation
- * for that foreground service type.
+ * for that foreground service type. See
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC dataSync} for example.
*
* <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
* foreground service type has a time limit of 6 hours, that time counter begins as soon as the
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index e4425ca..8c4667f 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -40,3 +40,13 @@
description: "Add a new callback in Service to indicate a FGS has reached its timeout."
bug: "317799821"
}
+
+flag {
+ namespace: "system_performance"
+ name: "app_start_info_timestamps"
+ description: "Additional timestamps."
+ bug: "287153617"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 7ac9547..4f06209 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1348,6 +1348,17 @@
public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
/**
+ * This change id restricts treatments that force a given min aspect ratio to
+ * only when an app is connected to the camera
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA = 325586858L; // buganizer id
+
+ /**
* This change id restricts treatments that force a given min aspect ratio to activities
* whose orientation is fixed to portrait.
*
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index c62680f..027d101 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -92,6 +92,7 @@
mTestedUsers = new ArraySet<>();
mUsersCleaningUp = new ArraySet<>();
setTestHalEnabled(true);
+ Log.d(getTag(), "Opening BiometricTestSession");
}
/**
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6b5e17d..b588308 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -180,3 +180,11 @@
description: "Use runtime permission state to determine appop state"
bug: "266164193"
}
+
+flag {
+ name: "device_id_in_op_proxy_info_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable getDeviceId API in OpEventProxyInfo"
+ bug: "337340961"
+}
\ No newline at end of file
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..c674968 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -70,6 +70,11 @@
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 4f;
+ private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0.2f;
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -494,9 +499,9 @@
drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
+ // Since high contrast text draws a thick border on the text, the highlight actually makes
+ // it harder to read. In this case we draw over the top of the text with a blend mode that
+ // ensures the text stays high-contrast.
if (shouldDrawHighlightsOnTop(canvas)) {
drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
cursorOffsetVertical, firstLine, lastLine);
@@ -922,6 +927,9 @@
public void drawBackground(
@NonNull Canvas canvas,
int firstLine, int lastLine) {
+
+ drawHighContrastBackground(canvas, firstLine, lastLine);
+
// First, draw LineBackgroundSpans.
// LineBackgroundSpans know nothing about the alignment, margins, or
// direction of the layout or line. XXX: Should they?
@@ -988,6 +996,66 @@
}
/**
+ * Draws a solid rectangle behind the text, the same color as the high contrast stroke border,
+ * to make it even easier to read.
+ *
+ * <p>We draw it here instead of in DrawTextFunctor so that multiple spans don't draw
+ * backgrounds over each other's text.
+ */
+ private void drawHighContrastBackground(@NonNull Canvas canvas, int firstLine, int lastLine) {
+ if (!shouldDrawHighlightsOnTop(canvas)) {
+ return;
+ }
+
+ var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
+ mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
+
+ var bgPaint = mWorkPlainPaint;
+ bgPaint.reset();
+ bgPaint.setColor(isHighContrastTextDark() ? Color.WHITE : Color.BLACK);
+ bgPaint.setStyle(Paint.Style.FILL);
+
+ int start = getLineStart(firstLine);
+ int end = getLineEnd(lastLine);
+ // Draw a separate background rectangle for each line of text, that only surrounds the
+ // characters on that line.
+ forEachCharacterBounds(
+ start,
+ end,
+ firstLine,
+ lastLine,
+ new CharacterBoundsListener() {
+ int mLastLineNum = -1;
+ final RectF mLineBackground = new RectF();
+
+ @Override
+ public void onCharacterBounds(int index, int lineNum, float left, float top,
+ float right, float bottom) {
+ if (lineNum != mLastLineNum) {
+ drawRect();
+ mLineBackground.set(left, top, right, bottom);
+ mLastLineNum = lineNum;
+ } else {
+ mLineBackground.union(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void onEnd() {
+ drawRect();
+ }
+
+ private void drawRect() {
+ if (!mLineBackground.isEmpty()) {
+ mLineBackground.inset(-padding, -padding);
+ canvas.drawRect(mLineBackground, bgPaint);
+ }
+ }
+ }
+ );
+ }
+
+ /**
* @param canvas
* @return The range of lines that need to be drawn, possibly empty.
* @hide
@@ -1682,7 +1750,7 @@
}
if (bounds == null) {
- throw new IllegalArgumentException("bounds can't be null.");
+ throw new IllegalArgumentException("bounds can't be null.");
}
final int neededLength = 4 * (end - start);
@@ -1698,6 +1766,34 @@
final int startLine = getLineForOffset(start);
final int endLine = getLineForOffset(end - 1);
+
+ forEachCharacterBounds(start, end, startLine, endLine,
+ (index, lineNum, left, lineTop, right, lineBottom) -> {
+ final int boundsIndex = boundsStart + 4 * (index - start);
+ bounds[boundsIndex] = left;
+ bounds[boundsIndex + 1] = lineTop;
+ bounds[boundsIndex + 2] = right;
+ bounds[boundsIndex + 3] = lineBottom;
+ });
+ }
+
+ /**
+ * Return the characters' bounds in the given range. The coordinates are in local text layout.
+ *
+ * @param start the start index to compute the character bounds, inclusive.
+ * @param end the end index to compute the character bounds, exclusive.
+ * @param startLine index of the line that contains {@code start}
+ * @param endLine index of the line that contains {@code end}
+ * @param listener called for each character with its bounds
+ *
+ */
+ private void forEachCharacterBounds(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @IntRange(from = 0) int startLine,
+ @IntRange(from = 0) int endLine,
+ CharacterBoundsListener listener
+ ) {
float[] horizontalBounds = null;
for (int line = startLine; line <= endLine; ++line) {
final int lineStart = getLineStart(line);
@@ -1722,13 +1818,10 @@
final float left = horizontalBounds[offset * 2] + lineStartPos;
final float right = horizontalBounds[offset * 2 + 1] + lineStartPos;
- final int boundsIndex = boundsStart + 4 * (index - start);
- bounds[boundsIndex] = left;
- bounds[boundsIndex + 1] = lineTop;
- bounds[boundsIndex + 2] = right;
- bounds[boundsIndex + 3] = lineBottom;
+ listener.onCharacterBounds(index, line, left, lineTop, right, lineBottom);
}
}
+ listener.onEnd();
}
/**
@@ -4443,4 +4536,15 @@
public Paint.FontMetrics getMinimumFontMetrics() {
return mMinimumFontMetrics;
}
+
+ /**
+ * Callback for {@link #forEachCharacterBounds(int, int, int, int, CharacterBoundsListener)}
+ */
+ private interface CharacterBoundsListener {
+ void onCharacterBounds(int index, int lineNum, float left, float top, float right,
+ float bottom);
+
+ /** Called after the last character has been sent to {@link #onCharacterBounds}. */
+ default void onEnd() {}
+ }
}
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index b9de93c..7653bdb 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -56,11 +56,18 @@
public static class BytesResult {
public final String value;
public final String units;
+ /**
+ * Content description of the {@link #units}.
+ * See {@link View#setContentDescription(CharSequence)}
+ */
+ public final String unitsContentDescription;
public final long roundedBytes;
- public BytesResult(String value, String units, long roundedBytes) {
+ public BytesResult(String value, String units, String unitsContentDescription,
+ long roundedBytes) {
this.value = value;
this.units = units;
+ this.unitsContentDescription = unitsContentDescription;
this.roundedBytes = roundedBytes;
}
}
@@ -271,20 +278,20 @@
final Locale locale = res.getConfiguration().getLocales().get(0);
final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits);
final String formattedNumber = numberFormatter.format(rounded.value);
- final String units;
+ // Since ICU does not give us access to the pattern, we need to extract the unit string
+ // from ICU, which we do by taking out the formatted number out of the formatted string
+ // and trimming the result of spaces and controls.
+ final String formattedMeasure = formatMeasureShort(
+ locale, numberFormatter, rounded.value, rounded.units);
+ final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
+ String units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
+ String unitsContentDescription = units;
if (rounded.units == MeasureUnit.BYTE) {
// ICU spells out "byte" instead of "B".
units = getByteSuffixOverride(res);
- } else {
- // Since ICU does not give us access to the pattern, we need to extract the unit string
- // from ICU, which we do by taking out the formatted number out of the formatted string
- // and trimming the result of spaces and controls.
- final String formattedMeasure = formatMeasureShort(
- locale, numberFormatter, rounded.value, rounded.units);
- final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
- units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
}
- return new BytesResult(formattedNumber, units, rounded.roundedBytes);
+ return new BytesResult(formattedNumber, units, unitsContentDescription,
+ rounded.roundedBytes);
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ddead88..a844370 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -912,12 +912,6 @@
private static final String AUTOFILL_LOG_TAG = "View.Autofill";
/**
- * The logging tag used by this class when logging verbose and chatty (high volume)
- * autofill-related messages.
- */
- private static final String AUTOFILL_CHATTY_LOG_TAG = "View.Autofill.Chatty";
-
- /**
* The logging tag used by this class when logging content capture-related messages.
*/
private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
@@ -8708,8 +8702,8 @@
@CallSuper
protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
@Nullable Rect previouslyFocusedRect) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ gainFocus);
}
if (gainFocus) {
@@ -8777,8 +8771,8 @@
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, this + " afm is not null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, this + " afm is not null");
}
if (enter) {
// We have not been laid out yet, hence cannot evaluate
@@ -8791,8 +8785,8 @@
// animation beginning. On the time, the view is not visible
// to the user. And then as the animation progresses, the view
// becomes visible to the user.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG,
"notifyEnterOrExitForAutoFillIfNeeded:"
+ " isLaidOut(): " + isLaidOut()
+ " isVisibleToUser(): " + isVisibleToUser()
@@ -11024,28 +11018,28 @@
}
private boolean isAutofillable() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isAutofillable() entered.");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
}
if (getAutofillType() == AUTOFILL_TYPE_NONE) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
}
return false;
}
final AutofillManager afm = getAutofillManager();
if (afm == null) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "AutofillManager is null");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "AutofillManager is null");
}
return false;
}
// Check whether view is not part of an activity. If it's not, return false.
if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
}
return false;
}
@@ -11056,9 +11050,8 @@
if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
|| (!isImportantForAutofill()
&& afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG,
- "isImportantForAutofill(): " + isImportantForAutofill()
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+ "afm.isAutofillable(): " + afm.isAutofillable(this));
}
return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
@@ -11066,8 +11059,8 @@
// If the previous condition is not met, fall back to the previous way to trigger fill
// request based on autofill importance instead.
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
}
return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
}
@@ -11083,8 +11076,8 @@
/** @hide */
public boolean canNotifyAutofillEnterExitEvent() {
- if (Log.isLoggable(AUTOFILL_CHATTY_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_CHATTY_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ " isAutofillable(): " + isAutofillable()
+ " isAttachedToWindow(): " + isAttachedToWindow());
}
@@ -17160,10 +17153,12 @@
/**
* Handle a key event before it is processed by any input method
- * associated with the view hierarchy. This can be used to intercept
+ * associated with the view hierarchy. This can be used to intercept
* key events in special situations before the IME consumes them; a
* typical example would be handling the BACK key to update the application's
- * UI instead of allowing the IME to see it and close itself.
+ * UI instead of allowing the IME to see it and close itself. Due to a bug,
+ * this function is not called for BACK key events on Android T and U, when
+ * the IME is shown.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f0d27da..f51d909 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4257,8 +4257,8 @@
// when the values are applicable.
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
- updateInfrequentCount();
setCategoryFromCategoryCounts();
+ updateInfrequentCount();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
if (!mIsFrameRateConflicted) {
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index ee6ba24..3b9b162 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -16,6 +16,8 @@
package android.window;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -27,6 +29,8 @@
import android.util.Log;
import android.view.ViewRootImpl;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
/**
@@ -280,7 +284,8 @@
sendStopDispatching();
}
- static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
+ @VisibleForTesting(visibility = PACKAGE)
+ public static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
@NonNull
private final IOnBackInvokedCallback mIOnBackInvokedCallback;
/**
@@ -327,7 +332,8 @@
* Subclass of ImeOnBackInvokedCallback indicating that a predictive IME back animation may be
* played instead of invoking the callback.
*/
- static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
+ @VisibleForTesting(visibility = PACKAGE)
+ public static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
DefaultImeOnBackAnimationCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
int priority) {
super(iCallback, id, priority);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 7f6678e..cf0c015 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -176,6 +176,12 @@
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if ((callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
+ || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
+ && !isOnBackInvokedCallbackEnabled()) {
+ // Fall back to compat back key injection if legacy back behaviour should be used.
+ return;
+ }
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 98ff3c6..cd13c4a 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -2,6 +2,13 @@
container: "system"
flag {
+ name: "disable_thin_letterboxing_reachability"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether reachability is disabled in case of thin letterboxing"
+ bug: "334077350"
+}
+
+flag {
name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
namespace: "large_screen_experiences_app_compat"
description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index a022842..7267eb8 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -190,17 +190,21 @@
return -1;
}
- jbyte* bufferBytes = NULL;
- if (buffer) {
- bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL);
+ bool is_dir_in = (endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN;
+ jbyte *bufferBytes = (jbyte *)malloc(length);
+
+ if (!is_dir_in && buffer) {
+ env->GetByteArrayRegion(buffer, start, length, bufferBytes);
}
- jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes + start, length, timeout);
+ jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes, length, timeout);
- if (bufferBytes) {
- env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0);
+ if (is_dir_in && buffer) {
+ env->SetByteArrayRegion(buffer, start, length, bufferBytes);
}
+ free(bufferBytes);
+
return result;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2106c5..77a9912 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8233,7 +8233,7 @@
</activity>
<activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
android:exported="false"
- android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:theme="@style/AccessibilityButtonChooserDialog"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d1be38..877d11e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -764,6 +764,9 @@
<!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
<bool name="config_unfoldTransitionHingeAngle">false</bool>
+ <!-- Indicates whether to enable haptics during unfold animation -->
+ <bool name="config_unfoldTransitionHapticsEnabled">false</bool>
+
<!-- Indicates the time needed to time out the fold animation if the device stops in half folded
mode. -->
<integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
@@ -7035,4 +7038,7 @@
If the gesture is completed faster than this, we assume it's not performed by human and the
event gets ignored. -->
<integer name="config_defaultMinEmergencyGestureTapDurationMillis">200</integer>
+
+ <!-- Whether the system uses auto-suspend mode. -->
+ <bool name="config_useAutoSuspend">true</bool>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5945f81..a46dc04 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1685,4 +1685,9 @@
<item name="android:paddingStart">8dp</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
+
+ <style name="AccessibilityButtonChooserDialog"
+ parent="@style/Theme.DeviceDefault.Resolver">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf1230..972d8df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4219,6 +4219,7 @@
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
+ <java-symbol type="bool" name="config_unfoldTransitionHapticsEnabled" />
<java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
@@ -5423,4 +5424,7 @@
<!-- Back swipe thresholds -->
<java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
<java-symbol type="dimen" name="back_progress_non_linear_factor" />
+
+ <!-- For PowerManagerService to determine whether to use auto-suspend mode -->
+ <java-symbol type="bool" name="config_useAutoSuspend" />
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5cc1ee4..7fb894a 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -86,6 +86,7 @@
import android.os.Parcelable;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -126,6 +127,7 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -1835,6 +1837,36 @@
Assert.assertEquals(bitmap, resultBitmap);
}
+ @Test
+ public void testGetWhen_zero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(0)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(0);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(n.creationTime);
+ }
+
+ @Test
+ public void testGetWhen_devProvidedNonZero() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setWhen(9)
+ .build();
+
+ mSetFlagsRule.disableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_SORT_SECTION_BY_TIME);
+
+ assertThat(n.getWhen()).isEqualTo(9);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index f60eff6..1c12362 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -18,9 +18,6 @@
import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,6 +44,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,7 +62,13 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final Expect expect = Expect.create();
+
+ // Line count when using MockLayout
private static final int LINE_COUNT = 5;
+ // Actual line count when using StaticLayout
+ private static final int STATIC_LINE_COUNT = 9;
private static final int LINE_HEIGHT = 12;
private static final int LINE_DESCENT = 4;
private static final CharSequence LAYOUT_TEXT = "alwei\t;sdfs\ndf @";
@@ -655,8 +660,8 @@
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawSelectionAndHighlight_drawsHighContrastSelectionAndHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -677,9 +682,12 @@
layout.draw(c, highlightPaths, highlightPaints, selectionPath, selectionPaint,
/* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 2;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -687,29 +695,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(2);
+ expect.that(highlightsFound).isEqualTo(2);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabled_testDrawHighlight_drawsHighContrastHighlight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -730,9 +735,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -740,29 +748,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
- assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ expect.that(drawCommand.paint.getBlendMode()).isNotNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn on top of text")
+ expect.withMessage("highlight is drawn on top of text")
.that(highlightsFound).isEqualTo(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextDisabledByDefault_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -782,9 +787,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -792,29 +800,26 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
@RequiresFlagsDisabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
public void highContrastTextEnabledButFlagOff_testDrawHighlight_drawsNormalHighlightBehind() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingMult, mSpacingAdd);
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
List<Path> highlightPaths = new ArrayList<>();
List<Paint> highlightPaints = new ArrayList<>();
@@ -835,9 +840,12 @@
layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
/* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
- var textsDrawn = LINE_COUNT;
+ var textsDrawn = STATIC_LINE_COUNT;
var highlightsDrawn = 1;
- assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+ var backgroundRectsDrawn = 0;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
var highlightsFound = 0;
var curLineIndex = 0;
@@ -845,33 +853,84 @@
MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
if (drawCommand.path != null) {
- assertThat(drawCommand.path).isEqualTo(selectionPath);
- assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
- assertThat(drawCommand.paint.getBlendMode()).isNull();
+ expect.that(drawCommand.path).isEqualTo(selectionPath);
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ expect.that(drawCommand.paint.getBlendMode()).isNull();
highlightsFound++;
} else if (drawCommand.text != null) {
- int start = layout.getLineStart(curLineIndex);
- int end = layout.getLineEnd(curLineIndex);
- assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
curLineIndex++;
- assertWithMessage("highlight is drawn behind text")
+ expect.withMessage("highlight is drawn behind text")
.that(highlightsFound).isGreaterThan(0);
}
}
- assertThat(highlightsFound).isEqualTo(1);
+ expect.that(highlightsFound).isEqualTo(1);
}
@Test
public void mockCanvasHighContrastOverridesCorrectly() {
var canvas = new MockCanvas(100, 100);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
canvas.setHighContrastTextEnabled(true);
- assertThat(canvas.isHighContrastTextEnabled()).isTrue();
+ expect.that(canvas.isHighContrastTextEnabled()).isTrue();
canvas.setHighContrastTextEnabled(false);
- assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ expect.that(canvas.isHighContrastTextEnabled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawLightText_drawsBlackBackgroundRects() {
+ mTextPaint.setColor(Color.parseColor("#CCAA33"));
+ Layout layout = new StaticLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = STATIC_LINE_COUNT;
+ var highlightsDrawn = 0;
+ var backgroundRectsDrawn = STATIC_LINE_COUNT;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
+
+ int numBackgroundsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.rect != null) {
+ numBackgroundsFound++;
+ expect.that(drawCommand.paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(drawCommand.rect.height()).isAtLeast(LINE_HEIGHT);
+ expect.that(drawCommand.rect.width()).isGreaterThan(0);
+ float expectedY = (numBackgroundsFound) * (LINE_HEIGHT + LINE_DESCENT);
+ expect.that(drawCommand.rect.bottom).isAtLeast(expectedY);
+ } else if (drawCommand.text != null) {
+ // draw text
+ curLineIndex++;
+
+ expect.withMessage("background is drawn on top of text")
+ .that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
+ } else {
+ fail("unexpected path drawn");
+ }
+ }
+
+ // One for each line
+ expect.that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
}
private static final class MockCanvas extends Canvas {
@@ -881,22 +940,46 @@
public final float x;
public final float y;
public final Path path;
+ public final RectF rect;
public final Paint paint;
DrawCommand(String text, float x, float y, Paint paint) {
this.text = text;
this.x = x;
this.y = y;
- this.paint = paint;
+ this.paint = new Paint(paint);
path = null;
+ rect = null;
}
DrawCommand(Path path, Paint paint) {
this.path = path;
- this.paint = paint;
+ this.paint = new Paint(paint);
y = 0;
x = 0;
text = null;
+ rect = null;
+ }
+
+ DrawCommand(RectF rect, Paint paint) {
+ this.rect = new RectF(rect);
+ this.paint = new Paint(paint);
+ path = null;
+ y = 0;
+ x = 0;
+ text = null;
+ }
+
+ @Override
+ public String toString() {
+ return "DrawCommand{"
+ + "text='" + text + '\''
+ + ", x=" + x
+ + ", y=" + y
+ + ", path=" + path
+ + ", rect=" + rect
+ + ", paint=" + paint
+ + '}';
}
}
@@ -956,6 +1039,11 @@
mDrawCommands.add(new DrawCommand(path, p));
}
+ @Override
+ public void drawRect(RectF rect, Paint p) {
+ mDrawCommands.add(new DrawCommand(rect, p));
+ }
+
List<DrawCommand> getDrawCommands() {
return mDrawCommands;
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 5caf77db..7c098f2 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1138,6 +1138,8 @@
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = sContext.getSystemService(WindowManager.class);
@@ -1157,8 +1159,6 @@
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
@@ -1168,8 +1168,6 @@
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
runAfterDraw(() -> assertEquals(expected,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
@@ -1177,12 +1175,26 @@
// Infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
+
+ // When the View vote, it's still considered as intermittent update state
+ sInstrumentation.runOnMainSync(() -> {
+ mView.invalidate();
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
+ });
+ waitForAfterDraw();
+
+ // Becomes frequent update state
+ sInstrumentation.runOnMainSync(() -> {
+ mView.invalidate();
+ runAfterDraw(() -> assertEquals(expected,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
+ });
}
/**
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 852d696..1a8e75a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -40,6 +40,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.IWindow;
import android.view.IWindowSession;
+import android.view.ImeBackAnimationController;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -77,6 +78,12 @@
@Mock
private OnBackAnimationCallback mCallback2;
@Mock
+ private ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback mImeCallback;
+ @Mock
+ private ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback mDefaultImeCallback;
+ @Mock
+ private ImeBackAnimationController mImeBackAnimationController;
+ @Mock
private Context mContext;
@Mock
private ApplicationInfo mApplicationInfo;
@@ -103,7 +110,7 @@
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper());
- mDispatcher.attachToWindow(mWindowSession, mWindow, null);
+ mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController);
}
private void waitForIdle() {
@@ -419,4 +426,28 @@
// onBackPressed is called from animator, so it can happen more than once.
verify(mCallback1, atLeast(1)).onBackProgressed(any());
}
+
+ @Test
+ public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mImeBackAnimationController);
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mImeCallback);
+ }
+
+ @Test
+ public void registerImeCallbacks_legacyBack() throws RemoteException {
+ doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ assertNoSetCallbackInfo();
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ assertNoSetCallbackInfo();
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 89d3058..e38038e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1520,12 +1520,16 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
if (taskFragmentContainer != null
&& taskFragmentContainer.isIsolatedNavigationEnabled()) {
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
+ return null;
+ }
+ if (isAssociatedWithOverlay(launchingActivity)) {
+ // Skip resolving if the launching activity associated with an overlay.
return null;
}
}
@@ -1659,9 +1663,8 @@
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
// TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
- final Rect taskBounds = taskContainer.getBounds();
final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
- getMinDimensions(intent), taskBounds);
+ getMinDimensions(intent), container);
final int windowingMode = taskContainer
.getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
@@ -1722,17 +1725,14 @@
@GuardedBy("mLock")
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
- // Check pending appeared activity first because there can be a delay for the server
- // update.
- TaskFragmentContainer taskFragmentContainer =
- getContainer(container -> container.hasPendingAppearedActivity(activityToken));
- if (taskFragmentContainer != null) {
- return taskFragmentContainer;
+ for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
+ final TaskFragmentContainer container = mTaskContainers.valueAt(i)
+ .getContainerWithActivity(activityToken);
+ if (container != null) {
+ return container;
+ }
}
-
-
- // Check appeared activity if there is no such pending appeared activity.
- return getContainer(container -> container.hasAppearedActivity(activityToken));
+ return null;
}
@GuardedBy("mLock")
@@ -2096,19 +2096,7 @@
if (container == null) {
return null;
}
- final List<SplitContainer> splitContainers =
- container.getTaskContainer().getSplitContainers();
- if (splitContainers.isEmpty()) {
- return null;
- }
- for (int i = splitContainers.size() - 1; i >= 0; i--) {
- final SplitContainer splitContainer = splitContainers.get(i);
- if (container.equals(splitContainer.getSecondaryContainer())
- || container.equals(splitContainer.getPrimaryContainer())) {
- return splitContainer;
- }
- }
- return null;
+ return container.getTaskContainer().getActiveSplitForContainer(container);
}
/**
@@ -2158,6 +2146,11 @@
return false;
}
+ if (isAssociatedWithOverlay(activity)) {
+ // Can't launch the placeholder if the activity associates an overlay.
+ return false;
+ }
+
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (container != null && !allowLaunchPlaceholder(container)) {
// We don't allow activity in this TaskFragment to launch placeholder.
@@ -2197,6 +2190,11 @@
*/
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
+ if (container.isOverlay()) {
+ // Don't launch placeholder if the container is an overlay.
+ return false;
+ }
+
final TaskFragmentContainer topContainer = container.getTaskContainer()
.getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
@@ -2470,13 +2468,10 @@
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (predicate.test(container)) {
- return container;
- }
+ final TaskFragmentContainer container = mTaskContainers.valueAt(i)
+ .getContainer(predicate);
+ if (container != null) {
+ return container;
}
}
return null;
@@ -2641,6 +2636,16 @@
return overlayContainers;
}
+ @GuardedBy("mLock")
+ private boolean isAssociatedWithOverlay(@NonNull Activity activity) {
+ final TaskContainer taskContainer = getTaskContainer(getTaskId(activity));
+ if (taskContainer == null) {
+ return false;
+ }
+ return taskContainer.getContainer(c -> c.isOverlay() && !c.isFinished()
+ && c.getAssociatedActivityToken() == activity.getActivityToken()) != null;
+ }
+
/**
* Creates an overlay container or updates a visible overlay container if its
* {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()}
@@ -2734,7 +2739,7 @@
}
// Requesting an always-on-top overlay.
if (!associateLaunchingActivity) {
- if (overlayContainer.isAssociatedWithActivity()) {
+ if (overlayContainer.isOverlayWithActivityAssociation()) {
// Dismiss the overlay container since it has associated with an activity.
Log.w(TAG, "The overlay container with tag:"
+ overlayContainer.getOverlayTag() + " is dismissed because"
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6231ea0..0e4fb30 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -591,16 +591,14 @@
@NonNull TaskFragmentContainer container,
@NonNull ActivityStackAttributes attributes,
@Nullable Size minDimensions) {
- final Rect taskBounds = container.getTaskContainer().getBounds();
final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
- taskBounds);
+ container);
final boolean isFillParent = relativeBounds.isEmpty();
// Note that we only set isolated navigation for overlay container without activity
// association. Activity will be launched to an expanded container on top of the overlay
// if the overlay is associated with an activity. Thus, an overlay with activity association
// will never be isolated navigated.
- final boolean isIsolatedNavigated = container.isOverlay()
- && !container.isAssociatedWithActivity() && !isFillParent;
+ final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent;
final boolean dimOnTask = !isFillParent
&& attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
&& Flags.fullscreenDimFlag();
@@ -624,7 +622,7 @@
*/
@NonNull
static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
- @NonNull Rect taskBounds) {
+ @NonNull TaskFragmentContainer container) {
if (bounds.isEmpty()) {
// Don't need to check if the bounds follows the task bounds.
return bounds;
@@ -633,10 +631,33 @@
// Expand the bounds if the bounds are smaller than minimum dimensions.
return new Rect();
}
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getBounds();
if (!taskBounds.contains(bounds)) {
// Expand the bounds if the bounds exceed the task bounds.
return new Rect();
}
+
+ if (!container.isOverlay()) {
+ // Stop here if the container is not an overlay.
+ return bounds;
+ }
+
+ final IBinder associatedActivityToken = container.getAssociatedActivityToken();
+
+ if (associatedActivityToken == null) {
+ // Stop here if the container is an always-on-top overlay.
+ return bounds;
+ }
+
+ // Expand the overlay with activity association if the associated activity is part of a
+ // split, or we may need to handle three change transition together.
+ final TaskFragmentContainer associatedContainer = taskContainer
+ .getContainerWithActivity(associatedActivityToken);
+ if (taskContainer.getActiveSplitForContainer(associatedContainer) != null) {
+ return new Rect();
+ }
+
return bounds;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index fdf0910..67d34c7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -43,6 +43,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
@@ -318,6 +319,38 @@
return null;
}
+ @Nullable
+ TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+ return getContainer(container -> container.hasAppearedActivity(activityToken)
+ || container.hasPendingAppearedActivity(activityToken));
+ }
+
+ @Nullable
+ TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
+ for (int i = mContainers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (predicate.test(container)) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
+ if (container == null) {
+ return null;
+ }
+ for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
+ final SplitContainer splitContainer = mSplitContainers.get(i);
+ if (container.equals(splitContainer.getSecondaryContainer())
+ || container.equals(splitContainer.getPrimaryContainer())) {
+ return splitContainer;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 094ebcb..c952dfe 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -465,7 +465,7 @@
return;
}
// Early return if this container is not an overlay with activity association.
- if (!isOverlay() || !isAssociatedWithActivity()) {
+ if (!isOverlayWithActivityAssociation()) {
return;
}
if (mAssociatedActivityToken == activityToken) {
@@ -500,7 +500,7 @@
// sure the controller considers this container as the one containing the activity.
// This is needed when the activity is added as pending appeared activity to one
// TaskFragment while it is also an appeared activity in another.
- return mController.getContainerWithActivity(activityToken) == this;
+ return mTaskContainer.getContainerWithActivity(activityToken) == this;
}
/** Whether this activity has appeared in the TaskFragment on the server side. */
@@ -1019,16 +1019,16 @@
return mAssociatedActivityToken;
}
- boolean isAssociatedWithActivity() {
- return mAssociatedActivityToken != null;
- }
-
/**
* Returns {@code true} if the overlay container should be always on top, which should be
* a non-fill-parent overlay without activity association.
*/
boolean isAlwaysOnTopOverlay() {
- return isOverlay() && !isAssociatedWithActivity();
+ return isOverlay() && mAssociatedActivityToken == null;
+ }
+
+ boolean isOverlayWithActivityAssociation() {
+ return isOverlay() && mAssociatedActivityToken != null;
}
@Override
@@ -1050,7 +1050,7 @@
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ " overlayTag=" + mOverlayTag
- + " associatedActivity" + mAssociatedActivityToken
+ + " associatedActivityToken=" + mAssociatedActivityToken
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 50abdfd..fab298d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -21,11 +21,15 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -85,6 +89,7 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -102,6 +107,9 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
+ new ComponentName("test", "placeholder"));
+
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
@@ -259,8 +267,7 @@
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag(),
- mOverlayContainer1.getTopNonFinishingActivity());
+ mOverlayContainer1.getOverlayTag());
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -280,12 +287,13 @@
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag(), mActivity);
+ mOverlayContainer1.getOverlayTag(), createMockActivity());
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is associated with different launching activity")
.that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(mOverlayContainer2, overlayContainer);
+ assertThat(overlayContainer).isNotEqualTo(mOverlayContainer1);
}
@Test
@@ -323,7 +331,8 @@
}
private void createExistingOverlayContainers(boolean visible) {
- mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible);
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible,
+ true /* associatedLaunchingActivity */, mActivity);
mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
List<TaskFragmentContainer> overlayContainers = mSplitController
.getAllNonFinishingOverlayContainers();
@@ -335,17 +344,49 @@
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
- SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
- TASK_BOUNDS);
+ assertThat(sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ overlayContainer).isEmpty()).isTrue();
}
@Test
public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
- SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
+ assertThat(sanitizeBounds(bounds, null, overlayContainer)
+ .isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testSanitizeBounds_visibleSplit_expandOverlay() {
+ // Launch a visible split
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity, true /* isVisible */);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity, true /* isVisible */);
+
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
+ .build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1", true /* isVisible */,
+ true /* associatedLaunchingActivity */, secondaryActivity);
+
+ assertThat(sanitizeBounds(bounds, null, overlayContainer)
+ .isEmpty()).isTrue();
}
@Test
@@ -701,6 +742,70 @@
.doesNotContain(overlayWithAssociation);
}
+ @Test
+ public void testLaunchPlaceholderIfNecessary_skipIfActivityAssociateOverlay() {
+ setupPlaceholderRule(mActivity);
+ createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
+ true /* associateLaunchingActivity */, mActivity);
+
+
+ mSplitController.mTransactionManager.startNewTransaction();
+ assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ false /* isOnCreated */)).isFalse();
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_skipIfActivityInOverlay() {
+ setupPlaceholderRule(mActivity);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);
+
+ mSplitController.mTransactionManager.startNewTransaction();
+ assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ false /* isOnCreated */)).isFalse();
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ /** Setups a rule to launch placeholder for the given activity. */
+ private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
+ final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT,
+ primaryActivity::equals, i -> false, w -> true)
+ .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+ .build();
+ mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_skipIfAssociateOverlay() {
+ final Intent intent = new Intent();
+ mSplitController.setEmbeddingRules(Collections.singleton(
+ createSplitRule(mActivity, intent)));
+ createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
+ true /* associateLaunchingActivity */, mActivity);
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+
+ assertThat(container).isNull();
+ verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
+ any());
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_skipIfLaunchingActivityInOverlay() {
+ final Intent intent = new Intent();
+ mSplitController.setEmbeddingRules(Collections.singleton(
+ createSplitRule(mActivity, intent)));
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+
+ assertThat(container).isNull();
+ verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
+ any());
+ }
+
/**
* A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@@ -726,9 +831,16 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ return createMockTaskFragmentContainer(activity, false /* isVisible */);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(
+ @NonNull Activity activity, boolean isVisible) {
final TaskFragmentContainer container = mSplitController.newContainer(activity,
activity.getTaskId());
- setupTaskFragmentInfo(container, activity, false /* isVisible */);
+ setupTaskFragmentInfo(container, activity, isVisible);
return container;
}
@@ -745,17 +857,26 @@
true /* associateLaunchingActivity */);
}
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+ boolean isVisible, boolean associateLaunchingActivity) {
+ return createTestOverlayContainer(taskId, tag, isVisible, associateLaunchingActivity,
+ null /* launchingActivity */);
+ }
+
// TODO(b/243518738): add more test coverage on overlay container without activity association
// once we have use cases.
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible, boolean associateLaunchingActivity) {
- Activity activity = createMockActivity();
+ boolean isVisible, boolean associateLaunchingActivity,
+ @Nullable Activity launchingActivity) {
+ final Activity activity = launchingActivity != null
+ ? launchingActivity : createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
null /* pendingAppearedActivity */, mIntent, activity, taskId,
null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
associateLaunchingActivity);
- setupTaskFragmentInfo(overlayContainer, activity, isVisible);
+ setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible);
return overlayContainer;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index abfc9c8..44ab2c4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -538,9 +538,7 @@
// container1.
container2.setInfo(mTransaction, mInfo);
- assertTrue(container1.hasActivity(mActivity.getActivityToken()));
- assertFalse(container2.hasActivity(mActivity.getActivityToken()));
-
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
// When the pending appeared record is removed from container1, we respect the appeared
// record in container2.
container1.removePendingAppearedActivity(mActivity.getActivityToken());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 4eff3f0..6e61f22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -26,6 +26,7 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -43,6 +44,7 @@
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -69,9 +71,11 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
PipTouchHandler pipTouchHandler,
- @NonNull PipScheduler pipScheduler) {
+ @NonNull PipScheduler pipScheduler,
+ @NonNull PipTransitionState pipStackListenerController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
+ pipStackListenerController);
}
@WMSingleton
@@ -85,6 +89,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -92,7 +99,7 @@
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
- mainExecutor));
+ taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
}
}
@@ -101,8 +108,8 @@
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
+ PipTransitionState pipTransitionState) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -146,4 +153,10 @@
return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
+
+ @WMSingleton
+ @Provides
+ static PipTransitionState providePipStackListenerController() {
+ return new PipTransitionState();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 4f71a02..7730285 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -54,7 +54,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -125,12 +124,8 @@
/**
* Called when the Shell wants to start resizing Pip transition/animation.
- *
- * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
- * client completes any potential draws upon WM state updates.
*/
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
// Default implementation does nothing.
}
@@ -266,9 +261,9 @@
}
/** Whether a particular package is same as current pip package. */
- public boolean isInPipPackage(String packageName) {
+ public boolean isPackageActiveInPip(String packageName) {
final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
- return packageName != null && inPipTask != null
+ return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
&& packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 1e18b8c..a12882f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -16,25 +16,31 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.InsetsState;
import android.view.SurfaceControl;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,6 +48,8 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TaskStackListenerCallback;
+import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,8 +65,11 @@
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
+ PipTransitionState.PipTransitionStateChangedListener,
DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
+ private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
+ private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
private final Context mContext;
private final ShellController mShellController;
@@ -68,6 +79,9 @@
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipScheduler mPipScheduler;
+ private final TaskStackListenerImpl mTaskStackListener;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
@@ -104,6 +118,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -113,6 +130,10 @@
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipScheduler = pipScheduler;
+ mTaskStackListener = taskStackListener;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
@@ -132,6 +153,9 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipDisplayLayoutState pipDisplayLayoutState,
PipScheduler pipScheduler,
+ TaskStackListenerImpl taskStackListener,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -140,7 +164,8 @@
}
return new PipController(context, shellInit, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
- pipScheduler, mainExecutor);
+ pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
+ mainExecutor);
}
private void onInit() {
@@ -164,6 +189,17 @@
mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
this::createExternalInterface, this);
mShellController.addConfigurationChangeListener(this);
+
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ return;
+ }
+ mPipScheduler.scheduleExitPipViaExpand();
+ }
+ });
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -245,11 +281,46 @@
Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onSwipePipToHomeAnimationStart: %s", componentName);
- mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
- overlay, appBounds);
+ Bundle extra = new Bundle();
+ extra.putParcelable(SWIPE_TO_PIP_OVERLAY, overlay);
+ extra.putParcelable(SWIPE_TO_PIP_APP_BOUNDS, appBounds);
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP, extra);
+ if (overlay != null) {
+ // Shell transitions might use a root animation leash, which will be removed when
+ // the Recents transition is finished. Launcher attaches the overlay leash to this
+ // animation target leash; thus, we need to reparent it to the actual Task surface now.
+ // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+ // transition.
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
+ tx.setLayer(overlay, Integer.MAX_VALUE);
+ tx.apply();
+ }
mPipRecentsAnimationListener.onPipAnimationStarted();
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ if (newState == PipTransitionState.SWIPING_TO_PIP) {
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ }
+ }
+
//
// IPipAnimationListener Binder proxy helpers
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index b4ca7df..72fa3ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -21,21 +21,16 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
@@ -43,7 +38,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.function.Consumer;
/**
* Scheduler for Shell initiated PiP transitions and animations.
@@ -55,31 +49,10 @@
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
- private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
- // pinned PiP task's WC token
- @Nullable
- private WindowContainerToken mPipTaskToken;
-
- // pinned PiP task's leash
- @Nullable
- private SurfaceControl mPinnedTaskLeash;
-
- // true if Launcher has started swipe PiP to home animation
- private boolean mInSwipePipToHomeTransition;
-
- // Overlay leash potentially used during swipe PiP to home transition;
- // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
- @Nullable
- SurfaceControl mSwipePipToHomeOverlay;
-
- // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
- // these are also used to calculate the app icon overlay buffer size.
- @NonNull
- final Rect mSwipePipToHomeAppBounds = new Rect();
-
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -118,11 +91,11 @@
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
- ShellTaskOrganizer shellTaskOrganizer) {
+ PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
- mShellTaskOrganizer = shellTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
if (PipUtils.isPip2ExperimentEnabled()) {
// temporary broadcast receiver to initiate exit PiP via expand
@@ -140,25 +113,17 @@
mPipTransitionController = pipTransitionController;
}
- void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
- mPinnedTaskLeash = pinnedTaskLeash;
- }
-
- void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
- mPipTaskToken = pipTaskToken;
- }
-
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
// final expanded bounds to be inherited from the parent
- wct.setBounds(mPipTaskToken, null);
+ wct.setBounds(mPipTransitionState.mPipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
return wct;
}
@@ -183,43 +148,12 @@
/**
* Animates resizing of the pinned stack given the duration.
*/
- public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
- if (mPipTaskToken == null) {
+ public void scheduleAnimateResizePip(Rect toBounds) {
+ if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTaskToken, toBounds);
- mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
- }
-
- void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
- mInSwipePipToHomeTransition = true;
- mSwipePipToHomeOverlay = overlay;
- mSwipePipToHomeAppBounds.set(appBounds);
- if (overlay != null) {
- // Shell transitions might use a root animation leash, which will be removed when
- // the Recents transition is finished. Launcher attaches the overlay leash to this
- // animation target leash; thus, we need to reparent it to the actual Task surface now.
- // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
- // transition.
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
- tx.setLayer(overlay, Integer.MAX_VALUE);
- tx.apply();
- }
- }
-
- void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
- mInSwipePipToHomeTransition = inSwipePipToHome;
- }
-
- boolean isInSwipePipToHomeTransition() {
- return mInSwipePipToHomeTransition;
- }
-
- void onExitPip() {
- mPipTaskToken = null;
- mPinnedTaskLeash = null;
+ wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index e829d4e..12dce5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -34,6 +34,7 @@
import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -43,6 +44,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -54,23 +56,33 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.function.Consumer;
-
/**
* Implementation of transitions for PiP on phone.
*/
-public class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController implements
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = PipTransition.class.getSimpleName();
+ private static final String PIP_TASK_TOKEN = "pip_task_token";
+ private static final String PIP_TASK_LEASH = "pip_task_leash";
+
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
* The fadeout animation is guaranteed to start after the client has drawn under the new config.
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
+ //
+ // Dependencies
+ //
+
private final Context mContext;
private final PipScheduler mPipScheduler;
- @Nullable
- private WindowContainerToken mPipTaskToken;
+ private final PipTransitionState mPipTransitionState;
+
+ //
+ // Transition tokens
+ //
+
@Nullable
private IBinder mEnterTransition;
@Nullable
@@ -78,7 +90,14 @@
@Nullable
private IBinder mResizeTransition;
- private Consumer<Rect> mFinishResizeCallback;
+ //
+ // Internal state and relevant cached info
+ //
+
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+ @Nullable
+ private SurfaceControl mPipLeash;
public PipTransition(
Context context,
@@ -88,13 +107,16 @@
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipScheduler pipScheduler) {
+ PipScheduler pipScheduler,
+ PipTransitionState pipTransitionState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
}
@Override
@@ -104,6 +126,10 @@
}
}
+ //
+ // Transition collection stage lifecycle hooks
+ //
+
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
@@ -117,13 +143,11 @@
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct,
- Consumer<Rect> onFinishResizeCallback) {
+ public void startResizeTransition(WindowContainerTransaction wct) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
- mFinishResizeCallback = onFinishResizeCallback;
}
@Nullable
@@ -146,6 +170,10 @@
}
}
+ //
+ // Transition playing stage lifecycle hooks
+ //
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -163,7 +191,19 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) {
mEnterTransition = null;
- if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition
+ // is being carried out.
+ TransitionInfo.Change pipChange = getPipChange(info);
+
+ // If there is no PiP change, exit this transition handler and potentially try others.
+ if (pipChange == null) return false;
+
+ Bundle extra = new Bundle();
+ extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
+ extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
// If this is the second transition as a part of swipe PiP to home cuj,
// handle this transition as a special case with no-op animation.
return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -179,9 +219,11 @@
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
+ mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS);
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
@@ -191,6 +233,10 @@
return false;
}
+ //
+ // Animation schedulers and entry points
+ //
+
private boolean startResizeAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -236,11 +282,7 @@
if (pipChange == null) {
return false;
}
- mPipScheduler.setInSwipePipToHomeTransition(false);
- mPipTaskToken = pipChange.getContainer();
-
- // cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
SurfaceControl pipLeash = pipChange.getLeash();
PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
@@ -264,9 +306,9 @@
} else {
final float scaleX = (float) destinationBounds.width() / startBounds.width();
final float scaleY = (float) destinationBounds.height() / startBounds.height();
- final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds);
- SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay;
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
+ mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
+ SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
.setScale(pipLeash, scaleX, scaleY)
@@ -274,7 +316,7 @@
.reparent(overlayLeash, pipLeash)
.setLayer(overlayLeash, Integer.MAX_VALUE);
- if (mPipTaskToken != null) {
+ if (pipTaskToken != null) {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
this::onClientDrawAtTransitionEnd)
@@ -282,7 +324,7 @@
.setPosition(overlayLeash,
(destinationBounds.width() - overlaySize) / 2f,
(destinationBounds.height() - overlaySize) / 2f);
- finishWct.setBoundsChangeTransaction(mPipTaskToken, tx);
+ finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
}
}
startTransaction.apply();
@@ -293,14 +335,6 @@
return true;
}
- private void onClientDrawAtTransitionEnd() {
- startOverlayFadeoutAnimation();
- }
-
- //
- // Subroutines setting up and starting transitions' animations.
- //
-
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -309,15 +343,17 @@
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.remove(mPipScheduler.mSwipePipToHomeOverlay);
+ tx.remove(mPipTransitionState.getSwipePipToHomeOverlay());
tx.apply();
- mPipScheduler.mSwipePipToHomeOverlay = null;
+
+ // We have fully completed enter-PiP animation after the overlay is gone.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
}
});
animator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply();
+ tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply();
});
animator.start();
}
@@ -330,10 +366,8 @@
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
@@ -349,10 +383,8 @@
if (pipChange == null) {
return false;
}
- mPipTaskToken = pipChange.getContainer();
-
// cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
+ WindowContainerToken pipTaskToken = pipChange.getContainer();
startTransaction.apply();
finishCallback.onTransitionFinished(null);
@@ -366,7 +398,7 @@
startTransaction.apply();
// TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
@@ -376,12 +408,20 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
finishCallback.onTransitionFinished(null);
- onExitPip();
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
return true;
}
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {
+ mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+ }
+
//
- // Utility methods for checking PiP-related transition info and requests.
+ // Various helpers to resolve transition requests and infos
//
@Nullable
@@ -442,11 +482,11 @@
}
private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
- if (mPipTaskToken == null) {
+ if (mPipTransitionState.mPipTaskToken == null) {
// PiP removal makes sense if enter-PiP has cached a valid pinned task token.
return false;
}
- TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
if (pipChange == null) {
// Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
return false;
@@ -460,14 +500,43 @@
return isPipMovedToBack || isPipClosed;
}
- /**
- * TODO: b/275910498 Use a new implementation of the PiP animator here.
- */
- private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
- Rect endBounds, int duration) {}
+ //
+ // Miscellaneous callbacks and listeners
+ //
- private void onExitPip() {
- mPipTaskToken = null;
- mPipScheduler.onExitPip();
+ private void onClientDrawAtTransitionEnd() {
+ if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
+ startOverlayFadeoutAnimation();
+ } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
+ // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
+ // and then we get a signal on client finishing its draw after the transition
+ // has ended, then we have fully entered PiP.
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ }
+ }
+
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.ENTERING_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ mPipTransitionState.mPipTaskToken = extra.getParcelable(
+ PIP_TASK_TOKEN, WindowContainerToken.class);
+ mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
+ PIP_TASK_LEASH, SurfaceControl.class);
+ boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
+ && mPipTransitionState.mPinnedTaskLeash != null;
+
+ Preconditions.checkState(hasValidTokenAndLeash,
+ "Unexpected bundle for " + mPipTransitionState);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mPipTransitionState.mPipTaskToken = null;
+ mPipTransitionState.mPinnedTaskLeash = null;
+ break;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
new file mode 100644
index 0000000..f7bc622
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains the state relevant to carry out or probe the status of PiP transitions.
+ *
+ * <p>Existing and new PiP components can subscribe to PiP transition related state changes
+ * via <code>PipTransitionStateChangedListener</code>.</p>
+ *
+ * <p><code>PipTransitionState</code> users shouldn't rely on listener execution ordering.
+ * For example, if a class <code>Foo</code> wants to change some arbitrary state A that belongs
+ * to some other class <code>Bar</code>, a special care must be given when manipulating state A in
+ * <code>Foo#onPipTransitionStateChanged()</code>, since that's the responsibility of
+ * the class <code>Bar</code>.</p>
+ *
+ * <p>Hence, the recommended usage for classes who want to subscribe to
+ * <code>PipTransitionState</code> changes is to manipulate only their own internal state or
+ * <code>PipTransitionState</code> state.</p>
+ *
+ * <p>If there is some state that must be manipulated in another class <code>Bar</code>, it should
+ * just be moved to <code>PipTransitionState</code> and become a shared state
+ * between Foo and Bar.</p>
+ *
+ * <p>Moreover, <code>onPipTransitionStateChanged(oldState, newState, extra)</code>
+ * receives a <code>Bundle</code> extra object that can be optionally set via
+ * <code>setState(state, extra)</code>. This can be used to resolve extra information to update
+ * relevant internal or <code>PipTransitionState</code> state. However, each listener
+ * needs to check for whether the extra passed is correct for a particular state,
+ * and throw an <code>IllegalStateException</code> otherwise.</p>
+ */
+public class PipTransitionState {
+ public static final int UNDEFINED = 0;
+
+ // State for Launcher animating the swipe PiP to home animation.
+ public static final int SWIPING_TO_PIP = 1;
+
+ // State for Shell animating enter PiP or jump-cutting to PiP mode after Launcher animation.
+ public static final int ENTERING_PIP = 2;
+
+ // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
+ public static final int ENTERED_PIP = 3;
+
+ // State for scheduling a transition to change PiP bounds.
+ public static final int CHANGING_PIP_BOUNDS = 4;
+
+ // State for app potentially finishing drawing in new PiP bounds after resize is complete.
+ public static final int CHANGED_PIP_BOUNDS = 5;
+
+ // State for starting exiting PiP.
+ public static final int EXITING_PIP = 6;
+
+ // State for finishing exit PiP flow.
+ public static final int EXITED_PIP = 7;
+
+ private static final int FIRST_CUSTOM_STATE = 1000;
+
+ private int mPrevCustomState = FIRST_CUSTOM_STATE;
+
+ @IntDef(prefix = { "TRANSITION_STATE_" }, value = {
+ UNDEFINED,
+ SWIPING_TO_PIP,
+ ENTERING_PIP,
+ ENTERED_PIP,
+ CHANGING_PIP_BOUNDS,
+ CHANGED_PIP_BOUNDS,
+ EXITING_PIP,
+ EXITED_PIP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransitionState {}
+
+ @TransitionState
+ private int mState;
+
+ //
+ // Swipe up to enter PiP related state
+ //
+
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
+ // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
+ // these are also used to calculate the app icon overlay buffer size.
+ @NonNull
+ private final Rect mSwipePipToHomeAppBounds = new Rect();
+
+ //
+ // Tokens and leashes
+ //
+
+ // pinned PiP task's WC token
+ @Nullable
+ WindowContainerToken mPipTaskToken;
+
+ // pinned PiP task's leash
+ @Nullable
+ SurfaceControl mPinnedTaskLeash;
+
+ // Overlay leash potentially used during swipe PiP to home transition;
+ // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
+ @Nullable
+ private SurfaceControl mSwipePipToHomeOverlay;
+
+ /**
+ * An interface to track state updates as we progress through PiP transitions.
+ */
+ public interface PipTransitionStateChangedListener {
+
+ /** Reports changes in PiP transition state. */
+ void onPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra);
+ }
+
+ private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
+
+ /**
+ * @return the state of PiP in the context of transitions.
+ */
+ @TransitionState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions.
+ */
+ public void setState(@TransitionState int state) {
+ setState(state, null /* extra */);
+ }
+
+ /**
+ * Sets the state of PiP in the context of transitions
+ *
+ * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
+ */
+ public void setState(@TransitionState int state, @Nullable Bundle extra) {
+ if (state == ENTERING_PIP || state == SWIPING_TO_PIP) {
+ // Whenever we are entering PiP caller must provide extra state to set as well.
+ Preconditions.checkArgument(extra != null && !extra.isEmpty(),
+ "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state.");
+ }
+ if (mState != state) {
+ dispatchPipTransitionStateChanged(mState, state, extra);
+ mState = state;
+ }
+ }
+
+ private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState, @Nullable Bundle extra) {
+ mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
+ }
+
+ /**
+ * Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
+ */
+ public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
+ if (mCallbacks.contains(listener)) {
+ return;
+ }
+ mCallbacks.add(listener);
+ }
+
+ /**
+ * @return true if provided {@link PipTransitionStateChangedListener}
+ * is registered before removing it.
+ */
+ public boolean removePipTransitionStateChangedListener(
+ PipTransitionStateChangedListener listener) {
+ return mCallbacks.remove(listener);
+ }
+
+ /**
+ * @return true if we have fully entered PiP.
+ */
+ public boolean isInPip() {
+ return mState > ENTERING_PIP && mState < EXITING_PIP;
+ }
+
+ void setSwipePipToHomeState(@Nullable SurfaceControl overlayLeash,
+ @NonNull Rect appBounds) {
+ mInSwipePipToHomeTransition = true;
+ if (overlayLeash != null && !appBounds.isEmpty()) {
+ mSwipePipToHomeOverlay = overlayLeash;
+ mSwipePipToHomeAppBounds.set(appBounds);
+ }
+ }
+
+ void resetSwipePipToHomeState() {
+ mInSwipePipToHomeTransition = false;
+ mSwipePipToHomeOverlay = null;
+ mSwipePipToHomeAppBounds.setEmpty();
+ }
+
+ /**
+ * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
+ */
+ public boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
+ /**
+ * @return the overlay used during swipe PiP to home for invalid srcRectHints in auto-enter PiP;
+ * null if srcRectHint provided is valid.
+ */
+ @Nullable
+ public SurfaceControl getSwipePipToHomeOverlay() {
+ return mSwipePipToHomeOverlay;
+ }
+
+ /**
+ * @return app bounds used to calculate
+ */
+ @NonNull
+ public Rect getSwipePipToHomeAppBounds() {
+ return mSwipePipToHomeAppBounds;
+ }
+
+ /**
+ * @return a custom state solely for internal use by the caller.
+ */
+ @TransitionState
+ public int getCustomState() {
+ return ++mPrevCustomState;
+ }
+
+ private String stateToString() {
+ switch (mState) {
+ case UNDEFINED: return "undefined";
+ case ENTERING_PIP: return "entering-pip";
+ case ENTERED_PIP: return "entered-pip";
+ case CHANGING_PIP_BOUNDS: return "changing-bounds";
+ case CHANGED_PIP_BOUNDS: return "changed-bounds";
+ case EXITING_PIP: return "exiting-pip";
+ case EXITED_PIP: return "exited-pip";
+ }
+ throw new IllegalStateException("Unknown state: " + mState);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
+ stateToString(), mInSwipePipToHomeTransition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2a50b19..5e9451a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -201,7 +201,7 @@
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
private final TransactionPool mTransactionPool;
- private final SplitScreenTransitions mSplitTransitions;
+ private SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private final ShellExecutor mMainExecutor;
// Cache live tile tasks while entering recents, evict them from stages in finish transaction
@@ -399,6 +399,11 @@
return mSplitTransitions;
}
+ @VisibleForTesting
+ void setSplitTransitions(SplitScreenTransitions splitScreenTransitions) {
+ mSplitTransitions = splitScreenTransitions;
+ }
+
public boolean isSplitScreenVisible() {
return mSideStageListener.mVisible && mMainStageListener.mVisible;
}
@@ -583,7 +588,7 @@
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+ if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -622,7 +627,7 @@
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
- if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+ if (mMixedHandler != null && mMixedHandler.isIntentInPip(intent)) {
mTaskOrganizer.applyTransaction(wct);
return;
}
@@ -711,16 +716,7 @@
taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
- if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- }
- if (mRecentTasks.isPresent()) {
- mRecentTasks.get().removeSplitPair(taskId1);
- }
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.startTask(taskId1, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ startSingleTask(taskId1, options1, wct, remoteTransition);
return;
}
@@ -741,11 +737,15 @@
"startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (taskId == INVALID_TASK_ID) {
- options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, null);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent);
+ boolean secondTaskPipped = mMixedHandler.isTaskInPip(taskId, mTaskOrganizer);
+ if (taskId == INVALID_TASK_ID || secondTaskPipped) {
+ startSingleIntent(pendingIntent, fillInIntent, options1, wct, remoteTransition);
+ return;
+ }
+
+ if (firstIntentPipped) {
+ startSingleTask(taskId, options2, wct, remoteTransition);
return;
}
@@ -757,6 +757,24 @@
startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
+ /**
+ * @param taskId Starts this task in fullscreen, removing it from existing pairs if it was part
+ * of one.
+ */
+ private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) {
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ if (mRecentTasks.isPresent()) {
+ mRecentTasks.get().removeSplitPair(taskId);
+ }
+ options = options != null ? options : new Bundle();
+ addActivityOptions(options, null);
+ wct.startTask(taskId, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a shortcut and a task to a split pair in one transition. */
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -844,6 +862,21 @@
return;
}
+ boolean handledForPipSplitLaunch = handlePippedSplitIntentsLaunch(
+ pendingIntent1,
+ pendingIntent2,
+ options1,
+ options2,
+ shortcutInfo1,
+ shortcutInfo2,
+ wct,
+ fillInIntent1,
+ fillInIntent2,
+ remoteTransition);
+ if (handledForPipSplitLaunch) {
+ return;
+ }
+
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -878,6 +911,46 @@
setEnterInstanceId(instanceId);
}
+ /**
+ * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will
+ * launch the non-pipped app as a fullscreen app, otherwise no-op.
+ */
+ private boolean handlePippedSplitIntentsLaunch(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2, Bundle options1, Bundle options2,
+ ShortcutInfo shortcutInfo1, ShortcutInfo shortcutInfo2, WindowContainerTransaction wct,
+ Intent fillInIntent1, Intent fillInIntent2, RemoteTransition remoteTransition) {
+ // If one of the split apps to start is in Pip, only launch the non-pip app in fullscreen
+ boolean firstIntentPipped = mMixedHandler.isIntentInPip(pendingIntent1);
+ boolean secondIntentPipped = mMixedHandler.isIntentInPip(pendingIntent2);
+ if (firstIntentPipped || secondIntentPipped) {
+ Bundle options = secondIntentPipped ? options1 : options2;
+ options = options == null ? new Bundle() : options;
+ addActivityOptions(options, null);
+ if (shortcutInfo1 != null || shortcutInfo2 != null) {
+ ShortcutInfo infoToLaunch = secondIntentPipped ? shortcutInfo1 : shortcutInfo2;
+ wct.startShortcut(mContext.getPackageName(), infoToLaunch, options);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ } else {
+ PendingIntent intentToLaunch = secondIntentPipped ? pendingIntent1 : pendingIntent2;
+ Intent fillInIntentToLaunch = secondIntentPipped ? fillInIntent1 : fillInIntent2;
+ startSingleIntent(intentToLaunch, fillInIntentToLaunch, options, wct,
+ remoteTransition);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /** @param pendingIntent Starts this intent in fullscreen */
+ private void startSingleIntent(PendingIntent pendingIntent, Intent fillInIntent, Bundle options,
+ WindowContainerTransaction wct,
+ RemoteTransition remoteTransition) {
+ Bundle optionsToLaunch = options != null ? options : new Bundle();
+ addActivityOptions(optionsToLaunch, null);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, optionsToLaunch);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ }
+
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4bc0dc0..4d02ec2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -563,22 +563,23 @@
/** Use to when split use intent to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(PendingIntent intent) {
+ public boolean isIntentInPip(PendingIntent intent) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+ return mPipHandler
+ .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
}
return false;
}
/** Use to when split use taskId to enter, check if this enter transition should be mixed or
* not.*/
- public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+ public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
// Check if this intent package is same as pip one or not, if true we want let the pip
// task enter split.
if (mPipHandler != null) {
- return mPipHandler.isInPipPackage(
+ return mPipHandler.isPackageActiveInPip(
SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
}
return false;
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index 5b2ffec..4dd14f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 9f7d9fc..5c86a38 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 882b200..aa70c09 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index f5a8655..bf040d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
index 51a55e35..c7c804f2 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index 05f937a..214bdfa 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -20,6 +20,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
new file mode 100644
index 0000000..bd8ac37
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.testing.AndroidTestingRunner;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.pip2.phone.PipTransitionState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit test against {@link PhoneSizeSpecSource}.
+ *
+ * This test mocks the PiP2 flag to be true.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipTransitionStateTest extends ShellTestCase {
+ private static final String EXTRA_ENTRY_KEY = "extra_entry_key";
+ private PipTransitionState mPipTransitionState;
+ private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener;
+ private Parcelable mEmptyParcelable;
+
+ @Before
+ public void setUp() {
+ mPipTransitionState = new PipTransitionState();
+ mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+ mEmptyParcelable = new Bundle();
+ }
+
+ @Test
+ public void testEnteredState_withoutExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNull(extra);
+ };
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test
+ public void testEnteredState_withExtra() {
+ mStateChangedListener = (oldState, newState, extra) -> {
+ Assert.assertEquals(PipTransitionState.ENTERED_PIP, newState);
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP, extra);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnteringState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSwipingToPipState_withoutExtra() {
+ mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP);
+ }
+
+ @Test
+ public void testCustomState_withExtra_thenEntered_withoutExtra() {
+ final int customState = mPipTransitionState.getCustomState();
+ mStateChangedListener = (oldState, newState, extra) -> {
+ if (newState == customState) {
+ Assert.assertNotNull(extra);
+ Assert.assertEquals(mEmptyParcelable, extra.getParcelable(EXTRA_ENTRY_KEY));
+ return;
+ } else if (newState == PipTransitionState.ENTERED_PIP) {
+ Assert.assertNull(extra);
+ return;
+ }
+ Assert.fail("Neither custom not ENTERED_PIP state is received.");
+ };
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.addPipTransitionStateChangedListener(mStateChangedListener);
+ mPipTransitionState.setState(customState, extra);
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d819261..d7c3835 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -40,10 +40,12 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,6 +53,7 @@
import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -74,6 +77,7 @@
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
@@ -111,6 +115,8 @@
private TransactionPool mTransactionPool;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
+ @Mock
+ private DefaultMixedHandler mDefaultMixedHandler;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -370,6 +376,96 @@
}
}
+ @Test
+ public void testSplitIntentAndTaskWithPippedApp_launchFullscreen() {
+ int taskId = 9;
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isTaskInPip(taskId, mTaskOrganizer)).thenReturn(true);
+ mStageCoordinator.startIntentAndTask(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*option1*/,
+ taskId,
+ null /*option2*/,
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
+ @Test
+ public void testSplitIntentsWithPippedApp_launchFullscreen() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ mStageCoordinator.setMixedHandler(mDefaultMixedHandler);
+ PendingIntent pendingIntent = mock(PendingIntent.class);
+ PendingIntent pendingIntent2 = mock(PendingIntent.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ when(remoteTransition.getDebugName()).thenReturn("");
+ // Test launching second task full screen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(1))
+ .startFullscreenTransition(any(), any());
+
+ // Test launching first intent fullscreen
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent)).thenReturn(false);
+ when(mDefaultMixedHandler.isIntentInPip(pendingIntent2)).thenReturn(true);
+ mStageCoordinator.startIntents(
+ pendingIntent,
+ null /*fillInIntent*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ pendingIntent2,
+ null /*fillInIntent2*/,
+ null /*shortcutInfo1*/,
+ new Bundle(),
+ 0 /*splitPosition*/,
+ 1 /*snapPosition*/,
+ remoteTransition /*remoteTransition*/,
+ null /*instanceId*/);
+ verify(splitScreenTransitions, times(2))
+ .startFullscreenTransition(any(), any());
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 1fcb692..cfca480 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -34,7 +34,9 @@
namespace android {
-inline constexpr int kHighContrastTextBorderWidth = 4;
+// These should match the constants in framework/base/core/java/android/text/Layout.java
+inline constexpr float kHighContrastTextBorderWidth = 4.0f;
+inline constexpr float kHighContrastTextBorderWidthFactor = 0.2f;
static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
const Paint& paint, Canvas* canvas) {
@@ -48,7 +50,16 @@
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
+
+ if (flags::high_contrast_text_small_text_rect()) {
+ paint->setStrokeWidth(
+ std::max(kHighContrastTextBorderWidth,
+ kHighContrastTextBorderWidthFactor * paint->getSkFont().getSize()));
+ } else {
+ auto borderWidthFactor = 0.04f;
+ paint->setStrokeWidth(kHighContrastTextBorderWidth +
+ borderWidthFactor * paint->getSkFont().getSize());
+ }
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
@@ -106,36 +117,7 @@
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- if (flags::high_contrast_text_small_text_rect()) {
- const SkFont& font = paint.getSkFont();
- auto padding = kHighContrastTextBorderWidth + 0.1f * font.getSize();
-
- // Draw the background only behind each glyph's bounds. We do this instead of using
- // the bounds of the entire layout, because the layout includes alignment whitespace
- // etc which can obscure other text from separate passes (e.g. emojis).
- // Merge all the glyph bounds into one rect for this line, since drawing a rect for
- // each glyph is expensive.
- SkRect glyphBounds;
- SkRect bgBounds;
- for (size_t i = start; i < end; i++) {
- auto glyph = layout.getGlyphId(i);
-
- font.getBounds(reinterpret_cast<const SkGlyphID*>(&glyph), 1, &glyphBounds,
- &paint);
- glyphBounds.offset(layout.getX(i), layout.getY(i));
-
- bgBounds.join(glyphBounds);
- }
-
- if (!bgBounds.isEmpty()) {
- bgBounds.offset(x, y);
- bgBounds.outset(padding, padding);
- canvas->drawRect(bgBounds.fLeft, bgBounds.fTop, bgBounds.fRight,
- bgBounds.fBottom, outlinePaint);
- }
- } else {
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
- }
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 45253bb..68b81db 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -22,6 +22,9 @@
<!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V -->
<integer name="config_chargingFastThreshold">7500000</integer>
+ <!-- Threshold in micro watts above which a charger is rated as "fast"; 20W -->
+ <integer name="config_chargingFastThreshold_v2">20000000</integer>
+
<!-- When true, show 1/2G networks as 3G. -->
<bool name="config_showMin3G">false</bool>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 2032328..f659e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -145,7 +145,8 @@
final int slowThreshold = context.getResources().getInteger(
R.integer.config_chargingSlowlyThreshold);
final int fastThreshold = context.getResources().getInteger(
- R.integer.config_chargingFastThreshold);
+ getFastChargingThresholdResId());
+
return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
maxChargingWattage > fastThreshold ? CHARGING_FAST :
@@ -382,7 +383,7 @@
< context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
return CHARGING_SLOWLY;
} else if (maxChargingMicroWatt
- > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+ > context.getResources().getInteger(getFastChargingThresholdResId())) {
return CHARGING_FAST;
} else {
return CHARGING_REGULAR;
@@ -410,4 +411,10 @@
return -1;
}
}
+
+ private static int getFastChargingThresholdResId() {
+ return BatteryUtils.isChargingStringV2Enabled()
+ ? R.integer.config_chargingFastThreshold_v2
+ : R.integer.config_chargingFastThreshold;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 327e470..ca3af53 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -27,6 +27,7 @@
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.List;
@@ -97,9 +98,18 @@
/** Used to override the system property to enable or reset for charging string V2. */
@VisibleForTesting
public static void setChargingStringV2Enabled(Boolean enabled) {
- SystemProperties.set(
- BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
- enabled == null ? "" : String.valueOf(enabled));
+ setChargingStringV2Enabled(enabled, true /* updateProperty */);
+ }
+
+ /** Used to override the system property to enable or reset for charging string V2. */
+ @VisibleForTesting
+ public static void setChargingStringV2Enabled(
+ @Nullable Boolean enabled, boolean updateProperty) {
+ if (updateProperty) {
+ SystemProperties.set(
+ BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
+ enabled == null ? "" : String.valueOf(enabled));
+ }
BatteryUtils.sChargingStringV2Enabled = enabled;
}
}
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
index 6c0c1a7..4940610 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -38,6 +38,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.android.settingslib.fuelgauge.BatteryUtils
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.util.Optional
@@ -253,12 +254,17 @@
private val maxChargingCurrent: Optional<Int>,
private val maxChargingVoltage: Optional<Int>,
private val expectedChargingSpeed: Int,
+ private val chargingStringV2Enabled: Boolean,
) {
val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun getChargingSpeed_() {
+ BatteryUtils.setChargingStringV2Enabled(
+ chargingStringV2Enabled,
+ false /* updateProperty */
+ )
val batteryChangedIntent =
Intent(Intent.ACTION_BATTERY_CHANGED).apply {
maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
@@ -278,37 +284,57 @@
"maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
Optional.empty<Int>(),
Optional.empty<Int>(),
- CHARGING_UNKNOWN
+ CHARGING_UNKNOWN,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
Optional.of(0),
Optional.of(0),
- CHARGING_UNKNOWN
+ CHARGING_UNKNOWN,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
Optional.of(1500000),
Optional.of(5000000),
- CHARGING_REGULAR
+ CHARGING_REGULAR,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
Optional.of(1000000),
Optional.of(5000000),
- CHARGING_REGULAR
+ CHARGING_REGULAR,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
Optional.of(1501000),
Optional.of(5000000),
- CHARGING_FAST
+ CHARGING_FAST,
+ false /* chargingStringV2Enabled */
),
arrayOf(
"maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
Optional.of(999999),
Optional.of(5000000),
- CHARGING_SLOWLY
+ CHARGING_SLOWLY,
+ false /* chargingStringV2Enabled */
+ ),
+ arrayOf(
+ "maxCurrent=3000000, maxVoltage=9000000 -> CHARGING_FAST",
+ Optional.of(3000000),
+ Optional.of(9000000),
+ CHARGING_FAST,
+ true /* chargingStringV2Enabled */
+ ),
+ arrayOf(
+ "maxCurrent=2200000, maxVoltage=9000000 -> CHARGING_REGULAR",
+ Optional.of(2200000),
+ Optional.of(9000000),
+ CHARGING_REGULAR,
+ true /* chargingStringV2Enabled */
),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index e07cd05..ec3c003 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -27,6 +27,7 @@
import com.android.compose.animation.scene.transitions
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
@@ -79,6 +80,7 @@
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -128,6 +130,10 @@
CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
+
+ // Touches on the notification shade in blank areas fall through to the glanceable hub. When the
+ // shade is showing, we block all touches in order to prevent this unwanted behavior.
+ Box(modifier = Modifier.fillMaxSize().allowGestures(touchesAllowed))
}
/** Scene containing the glanceable hub UI. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 7d56a67..dff9b3b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -105,6 +105,7 @@
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
@@ -841,19 +842,31 @@
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ val context = LocalContext.current
val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
-
+ val accessibilityLabel =
+ remember(model, context) {
+ model.providerInfo.loadLabel(context.packageManager).toString().trim()
+ }
+ val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
Box(
modifier =
- modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
- Modifier.pointerInput(Unit) {
- // consume tap to prevent the child view from triggering interactions with the
- // app widget
- observeTaps(shouldConsume = true) { _ ->
- viewModel.onOpenEnableWorkProfileDialog()
+ modifier
+ .thenIf(!viewModel.isEditMode && model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ // consume tap to prevent the child view from triggering interactions with
+ // the app widget
+ observeTaps(shouldConsume = true) { _ ->
+ viewModel.onOpenEnableWorkProfileDialog()
+ }
}
}
- }
+ .thenIf(viewModel.isEditMode) {
+ Modifier.semantics {
+ contentDescription = accessibilityLabel
+ onClick(label = clickActionLabel, action = null)
+ }
+ }
) {
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -865,6 +878,7 @@
// Remove the extra padding applied to AppWidgetHostView to allow widgets to
// occupy the entire box.
setPadding(0)
+ accessibilityDelegate = viewModel.widgetAccessibilityDelegate
}
},
update = {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index e7bfee3..33d2cc4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -40,6 +40,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -274,6 +275,9 @@
AnimatedVisibility(
modifier =
Modifier.matchParentSize()
+ // Avoid taking focus away from the content when using explore-by-touch with
+ // accessibility tools.
+ .clearAndSetSemantics {}
// Do not consume motion events in the highlighted item and pass them down to
// the content.
.pointerInteropFilter { false },
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index fb8a271..2f50bbd 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -62,7 +62,7 @@
NOTE: in case these instructions become stale and don't actually enable the
framework, please make sure `SceneContainerFlag.isEnabled` in the
-[`SceneContainerFlags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt)
+[`SceneContainerFlag.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt)
file evalutes to `true`.
1. Set a collection of **aconfig flags** to `true` by running the following
@@ -74,10 +74,9 @@
$ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true
$ adb shell device_config override systemui com.android.systemui.media_in_scene_container true
$ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true
- $ adb shell device_config override systemui com.android.systemui.notification_heads_up_refactor true
+ $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true
$ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true
$ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true
- $ adb shell device_config override systemui com.android.systemui.refactor_keyguard_dismiss_intent true
```
2. **Restart** System UI by issuing the following command:
```console
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9e9a002..569116c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -140,6 +140,7 @@
underTest =
CommunalViewModel(
testScope,
+ context.resources,
kosmos.keyguardTransitionInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
@@ -343,6 +344,30 @@
}
@Test
+ fun touchesAllowed_shadeNotExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard without any shade expansion.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ runCurrent()
+ assertThat(touchesAllowed).isTrue()
+ }
+
+ @Test
+ fun touchesAllowed_shadeExpanded() =
+ testScope.runTest {
+ val touchesAllowed by collectLastValue(underTest.touchesAllowed)
+
+ // On keyguard with shade fully expanded.
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setLockscreenShadeExpansion(1f)
+ runCurrent()
+ assertThat(touchesAllowed).isFalse()
+ }
+
+ @Test
fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
testScope.runTest {
val isFocusable by collectLastValue(underTest.isFocusable)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index d702330..360f284 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -33,6 +33,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -46,6 +47,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -317,6 +319,51 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetectedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Start going to AOD on first button push
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ value = 0f
+ ),
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f
+ ),
+ ),
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(100) // account for debouncing
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
fun testTransitionToLockscreen_onWakeUpFromAod_dismissibleKeyguard_securitySwipe() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 0a29821..3b6f6a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,20 +12,19 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
package com.android.systemui.keyguard.domain.interactor
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +32,10 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
@@ -50,15 +52,18 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
- val configRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
+ val shadeRepository = kosmos.fakeShadeRepository
+ val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val burnInProgress = 1f
private val burnInYOffset = 20
@@ -67,7 +72,6 @@
private lateinit var bouncerRepository: KeyguardBouncerRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var burnInInteractor: BurnInInteractor
- private lateinit var shadeRepository: FakeShadeRepository
private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,11 +79,22 @@
private lateinit var underTest: UdfpsKeyguardInteractor
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
bouncerRepository = FakeKeyguardBouncerRepository()
- shadeRepository = FakeShadeRepository()
fakeCommandQueue = FakeCommandQueue()
burnInInteractor =
BurnInInteractor(
@@ -93,10 +108,10 @@
underTest =
UdfpsKeyguardInteractor(
- configRepository,
burnInInteractor,
kosmos.keyguardInteractor,
- shadeRepository,
+ kosmos.shadeInteractor,
+ kosmos.shadeLockscreenInteractor,
dialogManager,
)
}
@@ -183,13 +198,13 @@
val qsProgress by collectLastValue(underTest.qsProgress)
assertThat(qsProgress).isEqualTo(0f)
- shadeRepository.setQsExpansion(.22f)
+ shadeTestUtil.setQsExpansion(.22f)
assertThat(qsProgress).isEqualTo(.44f)
- shadeRepository.setQsExpansion(.5f)
+ shadeTestUtil.setQsExpansion(.5f)
assertThat(qsProgress).isEqualTo(1f)
- shadeRepository.setQsExpansion(.7f)
+ shadeTestUtil.setQsExpansion(.7f)
assertThat(qsProgress).isEqualTo(1f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e3eca67..030aa24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -19,24 +19,25 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -53,32 +54,50 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import com.android.systemui.Flags as AConfigFlags
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardRootViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val communalRepository = kosmos.communalRepository
- private val screenOffAnimationController = kosmos.screenOffAnimationController
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
- private val dozeParameters = kosmos.dozeParameters
- private val shadeRepository = kosmos.fakeShadeRepository
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val communalRepository by lazy { kosmos.communalRepository }
+ private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
+ private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor }
+ private val dozeParameters by lazy { kosmos.dozeParameters }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val underTest by lazy { kosmos.keyguardRootViewModel }
private val viewState = ViewStateAccessor()
+ // add to init block
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+ )
+ }
}
@Test
@@ -336,10 +355,10 @@
testScope,
)
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -356,11 +375,11 @@
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha returns to 1.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(1f)
}
@@ -377,11 +396,11 @@
)
// Open the shade.
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
assertThat(alpha).isEqualTo(0f)
// Close the shade, alpha is still 0 since we're not on the lockscreen.
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
assertThat(alpha).isEqualTo(0f)
}
@@ -400,7 +419,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -419,7 +438,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
@@ -438,7 +457,7 @@
assertThat(alpha).isEqualTo(0f)
// Try pulling down shade and ensure the value doesn't change
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
assertThat(alpha).isEqualTo(0f)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 4907359..ec2cb04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -16,13 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
@@ -40,15 +42,29 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenContentViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
lateinit var underTest: LockscreenContentViewModel
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
with(kosmos) {
@@ -77,6 +93,7 @@
}
@Test
+ @DisableSceneContainer
fun clockSize_withLargeClock_true() =
with(kosmos) {
testScope.runTest {
@@ -87,6 +104,7 @@
}
@Test
+ @DisableSceneContainer
fun clockSize_withSmallClock_false() =
with(kosmos) {
testScope.runTest {
@@ -109,6 +127,7 @@
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
@@ -119,6 +138,7 @@
}
@Test
+ @DisableSceneContainer
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 776f1a5..bc9d257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
@@ -57,6 +58,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
class LockscreenSceneViewModelTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index 769a54a..13d6411 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -58,13 +58,16 @@
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
[email protected]
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class InternetTileDataInteractorTest : SysuiTestCase() {
@@ -141,6 +144,7 @@
underTest =
InternetTileDataInteractor(
context,
+ testScope.coroutineContext,
testScope.backgroundScope,
airplaneModeRepository,
connectivityRepository,
@@ -433,8 +437,44 @@
.isEqualTo(expectedCd)
}
+ /**
+ * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
+ * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
+ * on the mentioned context. Since that context does not have a looper assigned to it, the
+ * handler instantiation will throw a RuntimeException.
+ *
+ * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception
+ * So either we should make Robolectric behvase similar to the device test, or change this
+ * test to look for a different signal than the exception, when run by Robolectric. For now
+ * we just assume the test is not Robolectric.
+ */
+ @Test(expected = java.lang.RuntimeException::class)
+ fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
+ testScope.runTest {
+ assumeFalse(isRobolectricTest());
+
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ runCurrent()
+ }
+
+ /**
+ * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
+ * problem this test solves. The solution here is to assign a looper to the context via
+ * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
+ * creating the SignalDrawable.
+ */
+ @TestableLooper.RunWithLooper
@Test
- fun mobileDefault_usesNetworkNameAndIcon() =
+ fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
testScope.runTest {
val latest by
collectLastValue(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 818c19c..4e06855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -16,22 +16,26 @@
package com.android.systemui.volume.panel.ui.viewmodel
+import android.content.Intent
+import android.content.applicationContext
import android.content.res.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.volume.panel.componentByKey
import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
import com.android.systemui.volume.panel.unavailableCriteria
+import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -51,67 +55,49 @@
private lateinit var underTest: VolumePanelViewModel
- private fun initUnderTest() {
- underTest =
- VolumePanelViewModel(
- testableResources.resources,
- kosmos.testScope.backgroundScope,
- KosmosVolumePanelComponentFactory(kosmos),
- kosmos.fakeConfigurationController,
+ @Test
+ fun dismissingPanel_changesVisibility() = test {
+ testScope.runTest {
+ assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(underTest.volumePanelState.value.isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun orientationChanges_panelOrientationChanges() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ testableResources.overrideConfiguration(
+ Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
)
- }
+ assertThat(volumePanelState!!.orientation).isEqualTo(Configuration.ORIENTATION_PORTRAIT)
- @Test
- fun dismissingPanel_changesVisibility() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+ fakeConfigurationController.onConfigurationChanged(
+ Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
+ )
+ runCurrent()
- underTest.dismissPanel()
- runCurrent()
-
- assertThat(underTest.volumePanelState.value.isVisible).isFalse()
- }
+ assertThat(volumePanelState!!.orientation)
+ .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
}
}
@Test
- fun orientationChanges_panelOrientationChanges() {
- with(kosmos) {
- testScope.runTest {
- initUnderTest()
- val volumePanelState by collectLastValue(underTest.volumePanelState)
- testableResources.overrideConfiguration(
- Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+ fun components_areReturned() =
+ test({
+ componentByKey =
+ mapOf(
+ COMPONENT_1 to mockVolumePanelUiComponentProvider,
+ COMPONENT_2 to mockVolumePanelUiComponentProvider,
+ BOTTOM_BAR to mockVolumePanelUiComponentProvider,
)
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_PORTRAIT)
-
- fakeConfigurationController.onConfigurationChanged(
- Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
- )
- runCurrent()
-
- assertThat(volumePanelState!!.orientation)
- .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
- }
- }
- }
-
- @Test
- fun components_areReturned() {
- with(kosmos) {
+ criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+ }) {
testScope.runTest {
- componentByKey =
- mapOf(
- COMPONENT_1 to mockVolumePanelUiComponentProvider,
- COMPONENT_2 to mockVolumePanelUiComponentProvider,
- BOTTOM_BAR to mockVolumePanelUiComponentProvider,
- )
- criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
- initUnderTest()
-
val componentsLayout by collectLastValue(underTest.componentsLayout)
runCurrent()
@@ -124,11 +110,45 @@
assertThat(componentsLayout!!.bottomBarComponent.isVisible).isTrue()
}
}
+
+ @Test
+ fun dismissPanel_dismissesPanel() = test {
+ testScope.runTest {
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
}
+ @Test
+ fun dismissBroadcast_dismissesPanel() = test {
+ testScope.runTest {
+ runCurrent() // run the flows to let allow the receiver to be registered
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(DISMISS_ACTION),
+ )
+ runCurrent()
+
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
+ }
+
+ private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) =
+ with(kosmos) {
+ setup()
+ underTest = volumePanelViewModel
+ test()
+ }
+
private companion object {
const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 3b6b5a0..2a8f1b5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -66,6 +66,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -127,4 +128,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPasswordView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPasswordView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5aac653..76f6f59 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -31,6 +31,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index 6780e57..5879c11 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -67,6 +67,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -107,4 +108,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPatternView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index d991581..3f7b028 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -35,6 +35,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index 6c79d5a..b464fb3 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -74,6 +74,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
@@ -241,4 +242,4 @@
</androidx.constraintlayout.motion.widget.MotionLayout>
-</com.android.keyguard.KeyguardPINView>
\ No newline at end of file
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index f3cd9e4..2158073 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -32,6 +32,7 @@
<com.android.systemui.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
+ android:importantForAccessibility="noHideDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
index 045c19e..65d6e28 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
@@ -14,36 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillAlpha="0.3"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
index 5e012ab..0399b81 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
@@ -16,34 +16,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
+ android:fillAlpha="0.18"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
-
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
index d8a9a70..b0acbdc 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
@@ -16,32 +16,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:pathData="M15.632,8H14.902C14.707,8 14.553,8.155 14.543,8.35C14.367,11.692 11.693,14.417 8.356,14.552C8.158,14.56 8,14.712 8,14.909V15.552C8,15.84 8.172,16.009 8.377,16C12.493,15.808 15.808,12.494 16,8.378C16.009,8.172 15.837,8 15.632,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:pathData="M12.643,8C12.932,8 13.105,8.175 13.09,8.382C12.903,10.892 10.892,12.903 8.382,13.09C8.175,13.105 8,12.93 8,12.723V11.993C8,11.798 8.153,11.648 8.347,11.63C10.089,11.467 11.546,10.092 11.645,8.362C11.657,8.163 11.807,8 12.006,8L12.643,8Z"
android:fillColor="#fff"/>
<path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
index a80d3b4..2cab043 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -16,28 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:alpha="0.3"
- >
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
<path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:pathData="M7.743,10.057L6.14,8.451L5.205,9.386L6.105,10.266C6.684,10.832 6.69,11.76 6.119,12.333L5.065,13.391C4.78,13.676 4.407,13.818 4.035,13.818C3.662,13.818 3.289,13.676 3.004,13.391L0.425,10.807C-0.136,10.245 -0.143,9.338 0.41,8.768L1.404,7.716C1.69,7.421 2.07,7.273 2.45,7.273C2.817,7.273 3.184,7.411 3.467,7.688L4.425,8.624L5.369,7.68L1.712,4.026C1.57,3.884 1.57,3.654 1.712,3.512L3.513,1.711C3.655,1.57 3.885,1.57 4.027,1.711L7.684,5.366L8.608,4.442L7.701,3.534C7.14,2.972 7.134,2.065 7.686,1.495L8.68,0.443C8.966,0.148 9.346,0 9.726,0C10.093,0 10.46,0.138 10.743,0.415L13.381,2.992C13.959,3.557 13.965,4.488 13.394,5.061L12.341,6.118C12.056,6.403 11.684,6.545 11.311,6.545C10.938,6.545 10.565,6.403 10.281,6.118L9.379,5.214L8.456,6.137L10.059,7.743C10.2,7.885 10.2,8.115 10.058,8.257L8.257,10.057C8.115,10.199 7.885,10.199 7.743,10.057ZM10.412,4.182L11.053,4.83C11.195,4.973 11.427,4.974 11.569,4.831L12.104,4.294C12.247,4.151 12.245,3.919 12.101,3.777L11.451,3.142L10.412,4.182ZM8.99,2.744L9.645,3.406L10.671,2.379L9.989,1.713C9.844,1.571 9.61,1.576 9.471,1.724L8.984,2.239C8.85,2.381 8.852,2.604 8.99,2.744ZM3.395,9.653L2.713,8.986C2.568,8.844 2.334,8.849 2.194,8.997L1.708,9.512C1.574,9.654 1.576,9.878 1.714,10.017L2.369,10.679L3.395,9.653ZM4.825,11.051L4.176,10.416L3.136,11.455L3.777,12.103C3.919,12.247 4.151,12.247 4.293,12.104L4.828,11.567C4.971,11.424 4.969,11.192 4.825,11.051Z"
android:fillColor="#fff"/>
</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 19273ec..6bfd088 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -461,10 +461,11 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_screenshotEditor" translatable="false"></string>
- <!-- ComponentName for the file browsing app that the system would expect to be used in work
- profile. The icon for this app will be shown to the user when informing them that a
- screenshot has been saved to work profile. If blank, a default icon will be shown. -->
- <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+ <!-- ComponentName for the file browsing app that the system would expect to be used for
+ screenshots. The icon for this app will be shown to the user when informing them that a
+ screenshot has been saved to a different profile (e.g. work profile). If blank, a default
+ icon will be shown. -->
+ <string name="config_screenshotFilesApp" translatable="false"></string>
<!-- The component name of the screenshot editing activity that provides the App Clips flow.
The App Clips flow includes taking a screenshot, showing user screenshot cropping activity
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9c6e5f8..dcdd4f0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -257,6 +257,8 @@
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
<string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
+ <!-- Notification displayed when a screenshot is saved in the private profile. [CHAR LIMIT=NONE] -->
+ <string name="screenshot_private_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the private profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
<!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
@@ -1182,6 +1184,8 @@
<string name="accessibility_action_label_edit_widgets">Customize widgets</string>
<!-- Accessibility content description for communal hub. [CHAR LIMIT=NONE] -->
<string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>
+ <!-- Label for accessibility action to select a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_select_widget">select widget</string>
<!-- Related to user switcher --><skip/>
@@ -1722,9 +1726,7 @@
<string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
- <string name="satellite_connected_carrier_text">Connected to satellite</string>
- <!-- Text displayed indicating that the user is not connected to a satellite signal. -->
- <string name="satellite_not_connected_carrier_text">Not connected to satellite</string>
+ <string name="satellite_connected_carrier_text">Satellite SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
@@ -2264,6 +2266,9 @@
<!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description when QS tile would be added or moved, but the current position is not valid for adding or moving to [CHAR LIMIT=NONE] -->
+ <string name="accessibilit_qs_edit_tile_add_move_invalid_position">Position invalid.</string>
+
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b916fc2..91fb688 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -304,7 +303,7 @@
*/
@Override
public void finish(int targetUserId) {
- if (!RefactorKeyguardDismissIntent.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
@@ -614,7 +613,7 @@
* @param action callback to be invoked when keyguard disappear animation completes.
*/
public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return;
}
if (mCancelAction != null) {
@@ -908,7 +907,7 @@
mUiEventLogger.log(uiEvent, getSessionId());
}
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (authenticatedWithPrimaryAuth) {
mPrimaryBouncerInteractor.get()
.notifyKeyguardAuthenticatedPrimaryAuth(targetUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b8af59d..4c9af66 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -121,6 +121,9 @@
SystemProperties.getBoolean("debug.disable_screen_decorations", false);
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
+
+ private static final boolean sToolkitSetFrameRateReadOnly =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
private int mDebugColor = Color.RED;
@@ -892,6 +895,10 @@
lp.width = MATCH_PARENT;
lp.height = MATCH_PARENT;
lp.setTitle("ScreenDecorHwcOverlay");
+ if (sToolkitSetFrameRateReadOnly) {
+ lp.setFrameRateBoostOnTouchEnabled(false);
+ lp.setFrameRatePowerSavingsBalanced(false);
+ }
lp.gravity = Gravity.TOP | Gravity.START;
if (!mDebug) {
lp.setColorMode(ActivityInfo.COLOR_MODE_A8);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index c6716e4..68a69d3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -192,6 +192,7 @@
private final ShadeController mShadeController;
private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
private final StatusBarWindowCallback mNotificationShadeCallback;
+ private final ScreenshotHelper mScreenshotHelper;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
@@ -221,6 +222,7 @@
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
+ mScreenshotHelper = new ScreenshotHelper(mContext);
}
@Override
@@ -516,8 +518,7 @@
}
private void handleTakeScreenshot() {
- ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(
+ mScreenshotHelper.takeScreenshot(
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index a081ecc..db251fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.os.UserHandle
+import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -37,7 +38,7 @@
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
- /** Whether communal hub can be focused by accessibility tools. */
+ /** Whether communal hub should be focused by accessibility tools. */
open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
/** Whether widgets are currently being re-ordered. */
@@ -49,6 +50,9 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ /** Accessibility delegate to be set on CommunalAppWidgetHostView. */
+ open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 24ea7b6..1120466 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,11 +16,15 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.res.Resources
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,7 +35,9 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@@ -56,6 +62,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ @Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
@@ -108,6 +115,25 @@
}
.distinctUntilChanged()
+ override val widgetAccessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources
+ .getString(R.string.accessibility_action_label_edit_widgets)
+ .lowercase()
+ )
+ )
+ }
+ }
+
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
@@ -206,6 +232,14 @@
return !shadeInteractor.isAnyFullyExpanded.value
}
+ /**
+ * Whether touches should be disabled in communal.
+ *
+ * This is needed because the notification shade does not block touches in blank areas and these
+ * fall through to the glanceable hub, which we don't want.
+ */
+ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 0fe9bf4..840c3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -22,10 +22,8 @@
import android.graphics.Rect
import android.view.View
import android.view.ViewOutlineProvider
-import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
-import com.android.systemui.res.R
/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
@@ -44,25 +42,6 @@
init {
enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
enforcedRectangle = Rect()
-
- accessibilityDelegate =
- object : AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- // Hint user to long press in order to enter edit mode
- info.addAction(
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
- context.getString(
- R.string.accessibility_action_label_edit_widgets
- ).lowercase()
- )
- )
- }
- }
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 2fa42ec..7ced932 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -24,7 +24,6 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.screenshot.SmartActionsReceiver;
-import com.android.systemui.volume.VolumePanelDialogReceiver;
import dagger.Binds;
import dagger.Module;
@@ -59,15 +58,6 @@
*/
@Binds
@IntoMap
- @ClassKey(VolumePanelDialogReceiver.class)
- public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
- VolumePanelDialogReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
@ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 1fba737..c2843d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -168,11 +168,13 @@
keyguardInteractor.isKeyguardOccluded
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- ownerReason = "isOccluded = true",
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ ownerReason = "isOccluded = true",
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 4abd6c6..f385671 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -77,7 +77,9 @@
// transition, to ensure we don't transition while moving between, for example,
// *_BOUNCER -> LOCKSCREEN.
return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
- KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ KeyguardState.deviceIsAsleepInState(
+ transitionInteractor.currentTransitionInfoInternal.value.to
+ )
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 2850165..b2a24ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -225,10 +225,12 @@
if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!maybeHandleInsecurePowerGesture()) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
index f5cd767..63bfba6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -19,10 +19,10 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
import javax.inject.Inject
@@ -38,17 +38,16 @@
class UdfpsKeyguardInteractor
@Inject
constructor(
- configRepo: ConfigurationRepository,
burnInInteractor: BurnInInteractor,
keyguardInteractor: KeyguardInteractor,
- shadeRepository: ShadeRepository,
+ shadeInteractor: ShadeInteractor,
+ shadeLockscreenInteractor: ShadeLockscreenInteractor,
dialogManager: SystemUIDialogManager,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
val dozeAmount = keyguardInteractor.dozeAmount
- val scaleForResolution = configRepo.scaleForResolution
/** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
val burnInOffsets: Flow<Offsets> =
@@ -68,13 +67,14 @@
val dialogHideAffordancesRequest: Flow<Boolean> = dialogManager.hideAffordancesRequest
val qsProgress: Flow<Float> =
- shadeRepository.qsExpansion // swipe from top of LS
+ shadeInteractor.qsExpansion // swipe from top of LS
.map { (it * 2).coerceIn(0f, 1f) }
.onStart { emit(0f) }
val shadeExpansion: Flow<Float> =
combine(
- shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS
+ shadeLockscreenInteractor
+ .udfpsTransitionToFullShadeProgress, // swipe from middle of LS
keyguardInteractor.statusBarState, // quick swipe from middle of LS
) { shadeProgress, statusBarState ->
if (statusBarState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
deleted file mode 100644
index a43eb71..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the refactor_keyguard_dismiss_intent flag. */
-@Suppress("NOTHING_TO_INLINE")
-object RefactorKeyguardDismissIntent {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.refactorKeyguardDismissIntent()
-
- /**
- * 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/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index 93b3ba5..b293027 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -20,8 +20,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -40,7 +40,7 @@
) : CoreStartable {
override fun start() {
- if (!RefactorKeyguardDismissIntent.isEnabled) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index f77d012..ac24591 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -22,9 +22,9 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +44,7 @@
) : CoreStartable {
override fun start() {
- if (!RefactorKeyguardDismissIntent.isEnabled) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index 7e39a884..adc090d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -57,5 +57,15 @@
)
}
+ fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { startAlpha },
+ onFinish = { 1f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 24429fa..e0a3af6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -19,11 +19,11 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
@@ -48,7 +48,7 @@
) {
/** Common fade for scrim alpha values during *BOUNCER->GONE */
fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
- return if (RefactorKeyguardDismissIntent.isEnabled) {
+ return if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index a08a234..b1fa710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
import javax.inject.Inject
@@ -80,7 +80,7 @@
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
- if (RefactorKeyguardDismissIntent.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
@@ -104,7 +104,7 @@
/** Lockscreen alpha */
val lockscreenAlpha: Flow<Float> =
- if (RefactorKeyguardDismissIntent.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
keyguardDismissActionInteractor
.get()
.willAnimateDismissActionOnLockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 4e77d13..6a6eba1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -51,8 +51,7 @@
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
/** Check whether to use scene framework */
- fun isSceneContainerEnabled() =
- SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled
+ fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
/** Check whether to use media refactor code */
fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
deleted file mode 100644
index 77279f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the media_in_scene_container flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object MediaInSceneContainerFlag {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the flag enabled? */
- @JvmStatic
- inline val isEnabled
- get() = Flags.mediaInSceneContainer()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
index d863dcc..710142b6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
@@ -96,7 +96,7 @@
if (messages2 == null) {
return -1;
}
- return (int) (n2.when - n1.when);
+ return (int) (n2.getWhen() - n1.getWhen());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 042fb63f..4ee2db7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -75,10 +75,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- // set layer to make alpha animation of brightness slider nicer - otherwise elements
- // of slider are animated separately and it doesn't look good. See b/329244723
- setLayerType(LAYER_TYPE_HARDWARE, null);
-
mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
mQSPanel = findViewById(R.id.quick_settings_panel);
mHeader = findViewById(R.id.header);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 58858df..829c419 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -332,6 +332,14 @@
return mTiles.size();
}
+ public int getItemCountForAccessibility() {
+ if (mAccessibilityAction == ACTION_MOVE) {
+ return mEditIndex;
+ } else {
+ return getItemCount();
+ }
+ }
+
@Override
public boolean onFailedToRecycleView(Holder holder) {
holder.stopDrag();
@@ -406,6 +414,10 @@
} else if (selectable && mAccessibilityAction == ACTION_MOVE) {
info.state.contentDescription = mContext.getString(
R.string.accessibility_qs_edit_tile_move_to_position, position);
+ } else if (!selectable && (mAccessibilityAction == ACTION_MOVE
+ || mAccessibilityAction == ACTION_ADD)) {
+ info.state.contentDescription = mContext.getString(
+ R.string.accessibilit_qs_edit_tile_add_move_invalid_position);
} else {
info.state.contentDescription = info.state.label;
}
@@ -424,14 +436,15 @@
holder.mTileView.setOnClickListener(null);
holder.mTileView.setFocusable(true);
holder.mTileView.setFocusableInTouchMode(true);
+ holder.mTileView.setAccessibilityTraversalBefore(View.NO_ID);
if (mAccessibilityAction != ACTION_NONE) {
holder.mTileView.setClickable(selectable);
holder.mTileView.setFocusable(selectable);
holder.mTileView.setFocusableInTouchMode(selectable);
- holder.mTileView.setImportantForAccessibility(selectable
- ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
- : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+// holder.mTileView.setImportantForAccessibility(selectable
+// ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+// : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
if (selectable) {
holder.mTileView.setOnClickListener(new OnClickListener() {
@Override
@@ -911,4 +924,5 @@
int estimatedTileViewHeight = mTempTextView.getMeasuredHeight() * 2 + padding * 2;
mMinTileViewHeight = Math.max(minHeight, estimatedTileViewHeight);
}
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index fdc596b..eec5d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -26,6 +26,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -38,7 +39,9 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -48,6 +51,7 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -55,6 +59,7 @@
@Inject
constructor(
private val context: Context,
+ @Main private val mainCoroutineContext: CoroutineContext,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
@@ -111,42 +116,48 @@
notConnectedFlow
} else {
combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
- when (signalIcon) {
- is SignalIconModel.Cellular -> {
- val secondary =
- mobileDataContentConcat(
- networkNameModel.name,
- dataContentDescription
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ Triple(networkNameModel, signalIcon, dataContentDescription)
+ }
+ .mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+
+ val drawable =
+ withContext(mainCoroutineContext) { SignalDrawable(context) }
+ drawable.setLevel(signalIcon.level)
+ val loadedIcon = Icon.Loaded(drawable, null)
+
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription =
+ ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
)
-
- val stateLevel = signalIcon.level
- val drawable = SignalDrawable(context)
- drawable.setLevel(stateLevel)
- val loadedIcon = Icon.Loaded(drawable, null)
-
- InternetTileModel.Active(
- secondaryTitle = secondary,
- icon = loadedIcon,
- stateDescription = ContentDescription.Loaded(secondary.toString()),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
- }
- is SignalIconModel.Satellite -> {
- val secondary =
- signalIcon.icon.contentDescription.loadContentDescription(context)
- InternetTileModel.Active(
- secondaryTitle = secondary,
- iconId = signalIcon.icon.res,
- stateDescription = ContentDescription.Loaded(secondary),
- contentDescription = ContentDescription.Loaded(internetLabel),
- )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(
+ context
+ )
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
}
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 234eda8..cf33c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -27,8 +27,6 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent
-import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
@@ -44,12 +42,10 @@
ComposeLockscreen.isEnabled &&
KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
- MediaInSceneContainerFlag.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
NotificationsHeadsUpRefactor.isEnabled &&
PredictiveBackSysUiFlag.isEnabled &&
- DeviceEntryUdfpsRefactor.isEnabled &&
- RefactorKeyguardDismissIntent.isEnabled
+ DeviceEntryUdfpsRefactor.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/** The main aconfig flag. */
@@ -61,12 +57,10 @@
ComposeLockscreen.token,
KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
- MediaInSceneContainerFlag.token,
MigrateClocksToBlueprint.token,
NotificationsHeadsUpRefactor.token,
PredictiveBackSysUiFlag.token,
DeviceEntryUdfpsRefactor.token,
- RefactorKeyguardDismissIntent.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 7130fa1..5960462 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -3,15 +3,19 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.os.UserHandle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import com.android.systemui.screenshot.message.ProfileMessageController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* MessageContainerController controls the display of content in the screenshot message container.
@@ -20,7 +24,9 @@
@Inject
constructor(
private val workProfileMessageController: WorkProfileMessageController,
+ private val profileMessageController: ProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
+ @Application private val mainScope: CoroutineScope,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -42,43 +48,52 @@
detectionNoticeView.visibility = View.GONE
}
- // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on.
- fun onScreenshotTaken(userHandle: UserHandle) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle)
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
-
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- }
- }
-
fun onScreenshotTaken(screenshot: ScreenshotData) {
- val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> =
- screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+ if (screenshotPrivateProfileBehaviorFix()) {
+ mainScope.launch {
+ val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- // If work profile first run needs to show, bias towards that, otherwise show screenshot
- // detection notification if needed.
- if (workProfileData != null) {
- workProfileFirstRunView.visibility = View.VISIBLE
- detectionNoticeView.visibility = View.GONE
- workProfileMessageController.populateView(
- workProfileFirstRunView,
- workProfileData,
- this::animateOutMessageContainer
- )
- animateInMessageContainer()
- } else if (notifiedApps.isNotEmpty()) {
- detectionNoticeView.visibility = View.VISIBLE
- workProfileFirstRunView.visibility = View.GONE
- screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
- animateInMessageContainer()
+ // If profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (profileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ profileMessageController.bindView(workProfileFirstRunView, profileData) {
+ animateOutMessageContainer()
+ }
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
+ }
+ } else {
+ val workProfileData =
+ workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+
+ // If work profile first run needs to show, bias towards that, otherwise show screenshot
+ // detection notification if needed.
+ if (workProfileData != null) {
+ workProfileFirstRunView.visibility = View.VISIBLE
+ detectionNoticeView.visibility = View.GONE
+ workProfileMessageController.populateView(
+ workProfileFirstRunView,
+ workProfileData,
+ this::animateOutMessageContainer
+ )
+ animateInMessageContainer()
+ } else if (notifiedApps.isNotEmpty()) {
+ detectionNoticeView.visibility = View.VISIBLE
+ workProfileFirstRunView.visibility = View.GONE
+ screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+ animateInMessageContainer()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index c801ca5..b93aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -53,8 +53,9 @@
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
- val fileManager = fileManagerComponentName()
- ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
+ val fileManager =
+ fileManagerComponentName()
+ ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
try {
val info = packageManager.getActivityInfo(fileManager, ComponentInfoFlags.of(0L))
val icon = packageManager.getActivityIcon(fileManager)
@@ -103,9 +104,7 @@
context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
private fun fileManagerComponentName() =
- ComponentName.unflattenFromString(
- context.getString(R.string.config_sceenshotWorkProfileFilesApp)
- )
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 9b8d047..8235325 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -38,6 +38,7 @@
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
+import com.android.systemui.screenshot.message.MessageModule;
import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
@@ -51,7 +52,7 @@
/**
* Defines injectable resources for Screenshots
*/
-@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class})
+@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class, MessageModule.class})
public abstract class ScreenshotModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
new file mode 100644
index 0000000..9d0f87f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/MessageModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MessageModule {
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunResources(
+ impl: ProfileFirstRunFileResourcesImpl
+ ): ProfileFirstRunFileResources
+
+ @Binds
+ @SysUISingleton
+ fun bindPackageLabelIconProvider(impl: PackageLabelIconProviderImpl): PackageLabelIconProvider
+
+ @Binds
+ @SysUISingleton
+ fun bindProfileFirstRunSettings(impl: ProfileFirstRunSettingsImpl): ProfileFirstRunSettings
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt
new file mode 100644
index 0000000..fd073ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/PackageLabelIconProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import javax.inject.Inject
+
+data class LabeledIcon(
+ val label: CharSequence,
+ val badgedIcon: Drawable?,
+)
+
+/** An object that can fetch a label and icon for a given component. */
+interface PackageLabelIconProvider {
+ /**
+ * @return the label and icon for the given component.
+ * @throws PackageManager.NameNotFoundException if the component was not found.
+ */
+ suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon
+}
+
+class PackageLabelIconProviderImpl @Inject constructor(private val packageManager: PackageManager) :
+ PackageLabelIconProvider {
+
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ val info =
+ packageManager.getActivityInfo(componentName, PackageManager.ComponentInfoFlags.of(0L))
+ val icon = packageManager.getActivityIcon(componentName)
+ val badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
+ val label = info.loadLabel(packageManager)
+ return LabeledIcon(label, badgedIcon)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
new file mode 100644
index 0000000..e58c76d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunFileResources.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Provides various configuration resource values for the profile first run flow. */
+interface ProfileFirstRunFileResources {
+ /** @return the ComponentName for the Files app, if available. */
+ fun fileManagerComponentName(): ComponentName?
+
+ /** @return a default LabeledIcon describing the files app */
+ fun defaultFileApp(): LabeledIcon
+}
+
+class ProfileFirstRunFileResourcesImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName() =
+ ComponentName.unflattenFromString(context.getString(R.string.config_screenshotFilesApp))
+
+ override fun defaultFileApp() =
+ LabeledIcon(
+ context.getString(R.string.screenshot_default_files_app_name),
+ badgedIcon = null
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
new file mode 100644
index 0000000..5ec14a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileFirstRunSettings.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.content.Context
+import javax.inject.Inject
+
+/**
+ * An interfaces for the settings related to the profile first run experience, storing a bit
+ * indicating whether the user has already dismissed the message for the given profile.
+ */
+interface ProfileFirstRunSettings {
+ /** @return true if the user has already dismissed the first run message for this profile. */
+ fun messageAlreadyDismissed(profileType: ProfileMessageController.FirstRunProfile): Boolean
+ /**
+ * Update storage to reflect the fact that the user has dismissed a first run message for the
+ * given profile.
+ */
+ fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile)
+}
+
+class ProfileFirstRunSettingsImpl @Inject constructor(private val context: Context) :
+ ProfileFirstRunSettings {
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ val preferenceKey = preferenceKey(profileType)
+ return sharedPreference().getBoolean(preferenceKey, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ val preferenceKey = preferenceKey(profileType)
+ val editor = sharedPreference().edit()
+ editor.putBoolean(preferenceKey, true)
+ editor.apply()
+ }
+
+ private fun preferenceKey(profileType: ProfileMessageController.FirstRunProfile): String {
+ return when (profileType) {
+ ProfileMessageController.FirstRunProfile.WORK -> WORK_PREFERENCE_KEY
+ ProfileMessageController.FirstRunProfile.PRIVATE -> PRIVATE_PREFERENCE_KEY
+ }
+ }
+
+ private fun sharedPreference() =
+ context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ companion object {
+ const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
+ const val WORK_PREFERENCE_KEY = "work_profile_first_run"
+ const val PRIVATE_PREFERENCE_KEY = "private_profile_first_run"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
new file mode 100644
index 0000000..9212a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/message/ProfileMessageController.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import javax.inject.Inject
+
+/**
+ * Handles work profile and private profile first run, determining whether a first run UI should be
+ * shown and populating that UI if needed.
+ */
+class ProfileMessageController
+@Inject
+constructor(
+ private val packageLabelIconProvider: PackageLabelIconProvider,
+ private val fileResources: ProfileFirstRunFileResources,
+ private val firstRunSettings: ProfileFirstRunSettings,
+ private val profileTypes: ProfileTypeRepository,
+) {
+
+ /**
+ * @return a populated ProfileFirstRunData object if a profile first run message should be
+ * shown, otherwise null.
+ */
+ suspend fun onScreenshotTaken(userHandle: UserHandle?): ProfileFirstRunData? {
+ if (userHandle == null) return null
+ val profileType =
+ when (profileTypes.getProfileType(userHandle.identifier)) {
+ ProfileType.WORK -> FirstRunProfile.WORK
+ ProfileType.PRIVATE -> FirstRunProfile.PRIVATE
+ else -> return null
+ }
+
+ if (firstRunSettings.messageAlreadyDismissed(profileType)) {
+ return null
+ }
+
+ val fileApp =
+ runCatching {
+ fileResources.fileManagerComponentName()?.let { fileManager ->
+ packageLabelIconProvider.getPackageLabelIcon(fileManager, userHandle)
+ }
+ }
+ .getOrNull() ?: fileResources.defaultFileApp()
+
+ return ProfileFirstRunData(fileApp, profileType)
+ }
+
+ /**
+ * Use the provided ProfileFirstRunData to populate the profile first run UI in the given view.
+ */
+ fun bindView(view: ViewGroup, data: ProfileFirstRunData, animateOut: () -> Unit) {
+ if (data.labeledIcon.badgedIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(data.labeledIcon.badgedIcon)
+ }
+ val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ view.context.getString(messageTemplate(data.profileType), data.labeledIcon.label)
+ view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOut()
+ firstRunSettings.onMessageDismissed(data.profileType)
+ }
+ }
+
+ private fun messageTemplate(profile: FirstRunProfile): Int {
+ return when (profile) {
+ FirstRunProfile.WORK -> R.string.screenshot_work_profile_notification
+ FirstRunProfile.PRIVATE -> R.string.screenshot_private_profile_notification
+ }
+ }
+
+ data class ProfileFirstRunData(
+ val labeledIcon: LabeledIcon,
+ val profileType: FirstRunProfile,
+ )
+
+ enum class FirstRunProfile {
+ WORK,
+ PRIVATE
+ }
+
+ companion object {
+ const val TAG = "PrivateProfileMessageCtrl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index e051dab..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -63,6 +63,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ setLayerType(LAYER_TYPE_HARDWARE, null);
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 67211b1..4d4d177 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -244,6 +244,7 @@
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.StateFlow;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -3991,7 +3992,12 @@
mExpandLatencyTracking = false;
}
float maxPanelHeight = getMaxPanelTransitionDistance();
- if (mHeightAnimator == null) {
+ if (mHeightAnimator == null && !MigrateClocksToBlueprint.isEnabled()) {
+ // MigrateClocksToBlueprint - There is an edge case where swiping up slightly,
+ // and then swiping down will trigger overscroll logic. Even without this flag
+ // enabled, the notifications can then run into UDFPS. At this point it is
+ // safer to remove overscroll for this one case to prevent overlap.
+
// Split shade has its own overscroll logic
if (isTracking()) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
@@ -4055,6 +4061,11 @@
}
@Override
+ public StateFlow<Float> getUdfpsTransitionToFullShadeProgress() {
+ return mShadeRepository.getUdfpsTransitionToFullShadeProgress();
+ }
+
+ @Override
public Flow<Float> getLegacyPanelExpansion() {
return mShadeRepository.getLegacyShadeExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 0c41efd..9322d31 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
/** Empty implementation of ShadeViewController for variants with no shade. */
@@ -92,6 +93,7 @@
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
@Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
+ override val udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
}
class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5c79e1e..b934d63 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -28,7 +28,7 @@
* Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
* Settings can be expanded without the full shade expansion.
*/
- val qsExpansion: StateFlow<Float>
+ @Deprecated("Use ShadeInteractor.qsExpansion instead") val qsExpansion: StateFlow<Float>
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
index 2611092..987c016 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
@@ -15,9 +15,14 @@
*/
package com.android.systemui.shade.domain.interactor
+import kotlinx.coroutines.flow.StateFlow
+
/** Allows the lockscreen to control the shade. */
interface ShadeLockscreenInteractor {
+ /** Amount shade has expanded with regard to the UDFPS location */
+ val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+
/**
* Expand shade so that notifications are visible. Non-split shade: just expanding shade or
* collapsing QS when they're expanded. Split shade: only expanding shade, notifications are
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 318da55..6a8b9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -32,7 +33,12 @@
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val lockIconViewController: LockIconViewController,
+ shadeRepository: ShadeRepository,
) : ShadeLockscreenInteractor {
+
+ override val udfpsTransitionToFullShadeProgress =
+ shadeRepository.udfpsTransitionToFullShadeProgress
+
override fun expandToNotifications() {
changeToShadeScene()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d465973..4d7dacd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -29,6 +29,7 @@
import android.util.Log;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.shared.model.MediaData;
@@ -47,6 +48,7 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -75,6 +77,8 @@
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
+ private final Executor mBackgroundExecutor;
+
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
private String mMediaNotificationKey;
@@ -115,13 +119,15 @@
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
mContext = context;
mMediaListeners = new ArrayList<>();
mVisibilityProvider = visibilityProvider;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mBackgroundExecutor = backgroundExecutor;
setupNotifPipeline();
@@ -381,9 +387,11 @@
Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ mMediaController.getPackageName());
}
- mMediaController.unregisterCallback(mMediaListener);
+ mBackgroundExecutor.execute(() -> {
+ mMediaController.unregisterCallback(mMediaListener);
+ mMediaController = null;
+ });
}
- mMediaController = null;
}
public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 8a53e0c..c17da4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -68,6 +69,8 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import java.util.concurrent.Executor;
+
import javax.inject.Provider;
/**
@@ -94,14 +97,16 @@
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor backgroundExecutor) {
return new NotificationMediaManager(
context,
visibilityProvider,
notifPipeline,
notifCollection,
mediaDataManager,
- dumpManager);
+ dumpManager,
+ backgroundExecutor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 240ae0c..9c1d073 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1243,8 +1243,9 @@
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
+
return cmp;
};
@@ -1256,8 +1257,8 @@
if (cmp != 0) return cmp;
cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ o1.getRepresentativeEntry().getSbn().getNotification().getWhen(),
+ o2.getRepresentativeEntry().getSbn().getNotification().getWhen());
return cmp;
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
index 5ce1db2..f253100 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -89,7 +89,7 @@
var futureTime = Long.MAX_VALUE
groupEntry.children
.asSequence()
- .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .mapNotNull { child -> child.sbn.notification.getWhen().takeIf { it > 0 } }
.forEach { time ->
val isInThePast = currentTimeMillis - time > 0
if (isInThePast) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index fb67f7c..f98f77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -296,7 +296,9 @@
locationLookupByKey: (String) -> GroupLocation,
): NotificationEntry? = postedEntries.asSequence()
.filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
- .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+ .sortedBy { posted ->
+ -posted.entry.sbn.notification.getWhen()
+ }
.firstOrNull()
?.let { posted ->
posted.entry.takeIf { entry ->
@@ -317,7 +319,7 @@
.filter { locationLookupByKey(it.key) != GroupLocation.Detached }
.sortedWith(compareBy(
{ !mPostedEntries.contains(it.key) },
- { -it.sbn.notification.`when` },
+ { -it.sbn.notification.getWhen() },
))
.firstOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9619aca..bfc5932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -153,12 +153,12 @@
uiEventId = HUN_SUPPRESSED_OLD_WHEN
) {
private fun whenAge(entry: NotificationEntry) =
- systemClock.currentTimeMillis() - entry.sbn.notification.`when`
+ systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
override fun shouldSuppress(entry: NotificationEntry): Boolean =
when {
// Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp.
- entry.sbn.notification.`when` <= 0L -> false
+ entry.sbn.notification.getWhen() <= 0L -> false
// Assume all HUNs with FSIs, foreground services, or user-initiated jobs are
// time-sensitive, regardless of their "when".
@@ -278,7 +278,7 @@
private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
- entry.sbn.notification.`when` > avalancheProvider.startTime
+ entry.sbn.notification.getWhen() > avalancheProvider.startTime
) {
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index d591114..9c6a423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -623,7 +623,7 @@
return false;
}
- final long when = notification.when;
+ final long when = notification.getWhen();
final long now = mSystemClock.currentTimeMillis();
final long age = now - when;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bdeaabf..5e3df7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2769,7 +2769,7 @@
}
if (!mIsSummaryWithChildren && wasSummary) {
// Reset the 'when' once the row stops being a summary
- mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().when);
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 37bbbd0..ac4bd09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -41,6 +41,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -106,6 +107,7 @@
private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
+ private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
@@ -464,6 +466,7 @@
val alphaTransitions =
merge(
alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
+ aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b2b2cea..7630d43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -855,6 +855,7 @@
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBubblesOptional.ifPresent(this::initBubbles);
+ mKeyguardBypassController.listenForQsExpandedChange();
mStatusBarSignalPolicy.init();
mKeyguardIndicationController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a8941bb..97791ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,7 +21,6 @@
import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
-import androidx.annotation.VisibleForTesting
import com.android.app.tracing.ListenersTracing.forEachTraced
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -32,7 +31,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -41,23 +40,24 @@
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
+import dagger.Lazy
+import java.io.PrintWriter
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import javax.inject.Inject
@SysUISingleton
class KeyguardBypassController @Inject constructor(
@Main resources: Resources,
packageManager: PackageManager,
- @Application applicationScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
tunerService: TunerService,
private val statusBarStateController: StatusBarStateController,
lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardStateController: KeyguardStateController,
- private val shadeRepository: ShadeRepository,
+ private val shadeInteractorLazy: Lazy<ShadeInteractor>,
devicePostureController: DevicePostureController,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
dumpManager: DumpManager
@@ -144,7 +144,6 @@
}
}
})
- listenForQsExpandedChange(applicationScope)
val dismissByDefault = if (resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable({ key, _ ->
@@ -159,10 +158,9 @@
}
}
- @VisibleForTesting
- fun listenForQsExpandedChange(scope: CoroutineScope) =
- scope.launch {
- shadeRepository.qsExpansion.map { it > 0f }.distinctUntilChanged()
+ fun listenForQsExpandedChange() =
+ applicationScope.launch {
+ shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
qsExpanded = isQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 68d54e7..6b68511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -131,7 +131,12 @@
val runnable = Runnable {
assistManagerLazy.get().hideAssist()
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ intent.flags =
+ if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT != 0) {
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ } else {
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
intent.addFlags(flags)
val result = intArrayOf(ActivityManager.START_CANCELED)
activityTransitionAnimator.startIntentWithAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f35d199..7301b87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -72,7 +72,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
-import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -800,7 +799,7 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (r == null) {
return;
}
@@ -852,7 +851,7 @@
return;
}
- if (!RefactorKeyguardDismissIntent.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
mDismissActionWillAnimateOnKeyguard = r != null
@@ -920,7 +919,7 @@
* Adds a {@param runnable} to be executed after Keyguard is gone.
*/
public void addAfterKeyguardGoneRunnable(Runnable runnable) {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (runnable != null) {
mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
}
@@ -1112,7 +1111,7 @@
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
// go directly from bouncer to launcher/app.
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
updateStates();
}
@@ -1239,7 +1238,7 @@
}
private void executeAfterKeyguardGoneAction() {
- if (RefactorKeyguardDismissIntent.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return;
}
if (mAfterKeyguardGoneAction != null) {
@@ -1629,8 +1628,8 @@
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
pw.println(" Registered KeyguardViewManagerCallbacks:");
- pw.println(" refactorKeyguardDismissIntent enabled:"
- + RefactorKeyguardDismissIntent.isEnabled());
+ pw.println(" SceneContainerFlag enabled:"
+ + SceneContainerFlag.isEnabled());
for (KeyguardViewManagerCallback callback : mCallbacks) {
pw.println(" " + callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a20468f..ec88b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -98,7 +98,7 @@
(entry.sbn.key == callNotificationInfo?.key)) {
val newOngoingCallInfo = CallNotificationInfo(
entry.sbn.key,
- entry.sbn.notification.`when`,
+ entry.sbn.notification.getWhen(),
entry.sbn.notification.contentIntent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index f2255f3..332c121 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -126,10 +126,9 @@
) { shouldShow, connectionState ->
if (shouldShow) {
when (connectionState) {
+ SatelliteConnectionState.On,
SatelliteConnectionState.Connected ->
context.getString(R.string.satellite_connected_carrier_text)
- SatelliteConnectionState.On ->
- context.getString(R.string.satellite_not_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e65566..b3ea9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -6,8 +6,8 @@
import android.os.Vibrator
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider
-import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -18,6 +18,7 @@
constructor(
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
foldProvider: FoldProvider,
+ transitionConfig: UnfoldTransitionConfig,
@Main private val mainExecutor: Executor,
private val vibrator: Vibrator?
) : TransitionProgressListener {
@@ -27,22 +28,17 @@
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
init {
- if (vibrator != null) {
+ if (vibrator != null && transitionConfig.isHapticsEnabled) {
// We don't need to remove the callback because we should listen to it
// the whole time when SystemUI process is alive
unfoldTransitionProgressProvider.addCallback(this)
- }
- foldProvider.registerCallback(
- object : FoldCallback {
- override fun onFoldUpdated(isFolded: Boolean) {
- if (isFolded) {
- isFirstAnimationAfterUnfold = true
- }
+ foldProvider.registerCallback({ isFolded ->
+ if (isFolded) {
+ isFirstAnimationAfterUnfold = true
}
- },
- mainExecutor
- )
+ }, mainExecutor)
+ }
}
private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
index f11d5d1..0968bde 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
@@ -21,26 +21,29 @@
import android.content.Intent
import android.provider.Settings
import android.text.TextUtils
-import android.util.Log
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor
+import com.android.systemui.volume.ui.navigation.VolumeNavigator
import javax.inject.Inject
-private const val TAG = "VolumePanelDialogReceiver"
-private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
-private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
-
-/**
- * BroadcastReceiver for handling volume panel dialog intent
- */
-class VolumePanelDialogReceiver @Inject constructor(
- private val volumePanelFactory: VolumePanelFactory
+/** [BroadcastReceiver] for handling volume panel dialog intent */
+class VolumePanelDialogReceiver
+@Inject
+constructor(
+ private val volumeNavigator: VolumeNavigator,
+ private val volumePanelNavigationInteractor: VolumePanelNavigationInteractor,
) : BroadcastReceiver() {
+
override fun onReceive(context: Context, intent: Intent) {
- Log.d(TAG, "onReceive intent" + intent.action)
- if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
- TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
- volumePanelFactory.create(true, null)
- } else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
- volumePanelFactory.dismiss()
+ if (
+ TextUtils.equals(LAUNCH_ACTION, intent.action) ||
+ TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)
+ ) {
+ volumeNavigator.openVolumePanel(volumePanelNavigationInteractor.getVolumePanelRoute())
}
}
+
+ companion object {
+ const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
+ const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index fb92a0f..dc1e8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
@@ -37,6 +38,7 @@
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
+import com.android.systemui.volume.VolumePanelDialogReceiver;
import com.android.systemui.volume.VolumeUI;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
@@ -65,6 +67,15 @@
}
)
public interface VolumeModule {
+
+ /**
+ * Binds [VolumePanelDialogReceiver]
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumePanelDialogReceiver.class)
+ BroadcastReceiver bindVolumePanelDialogReceiver(VolumePanelDialogReceiver receiver);
+
/** Starts VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index eff408a..a30de1b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -17,11 +17,14 @@
package com.android.systemui.volume.panel.ui.viewmodel
import android.content.Context
+import android.content.IntentFilter
import android.content.res.Resources
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.VolumePanelDialogReceiver
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.VolumePanelStartable
@@ -37,6 +40,8 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -49,6 +54,7 @@
coroutineScope: CoroutineScope,
daggerComponentFactory: VolumePanelComponentFactory,
configurationController: ConfigurationController,
+ broadcastDispatcher: BroadcastDispatcher,
) {
private val volumePanelComponent: VolumePanelComponent =
@@ -113,6 +119,10 @@
init {
volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION))
+ .onEach { dismissPanel() }
+ .launchIn(scope)
}
fun dismissPanel() {
@@ -125,6 +135,7 @@
@Application private val context: Context,
private val daggerComponentFactory: VolumePanelComponentFactory,
private val configurationController: ConfigurationController,
+ private val broadcastDispatcher: BroadcastDispatcher,
) {
fun create(coroutineScope: CoroutineScope): VolumePanelViewModel {
@@ -133,6 +144,7 @@
coroutineScope,
daggerComponentFactory,
configurationController,
+ broadcastDispatcher
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 4684b80..79e312f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -27,9 +27,11 @@
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
@@ -229,6 +231,11 @@
givenOnOccludingApp(true)
givenFingerprintAllowed(true)
keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.DOZING,
+ testScope
+ )
runCurrent()
// ERROR message
@@ -254,7 +261,7 @@
assertThat(message).isNull()
}
- private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ private suspend fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
powerRepository.setInteractive(true)
keyguardRepository.setIsDozing(false)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
@@ -262,6 +269,20 @@
keyguardRepository.setDreaming(false)
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+
+ if (isOnOccludingApp) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ } else {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ testScope
+ )
+ }
}
private fun givenFingerprintAllowed(allowed: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 96b7596..35659c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
@@ -83,6 +84,7 @@
)
@SmallTest
@RunWith(Parameterized::class)
+@DisableSceneContainer
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
new file mode 100644
index 0000000..ef3183a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@FlakyTest(
+ bugId = 292574995,
+ detail = "on certain architectures all permutations with startActivity=true is causing failures"
+)
+@SmallTest
+@RunWith(Parameterized::class)
+@EnableSceneContainer
+class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+ private val DRAWABLE =
+ mock<Icon> {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ )
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
+ @Mock private lateinit var launchAnimator: DialogTransitionAnimator
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+ @JvmField @Parameter(4) var startActivity: Boolean = false
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var dockManager: DockManagerFake
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var userTracker: UserTracker
+
+ private val kosmos = testKosmos()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(expandable.activityTransitionController()).thenReturn(animationController)
+
+ userTracker = FakeUserTracker()
+ homeControls =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ dockManager = DockManagerFake()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val featureFlags = FakeFeatureFlags()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ underTest =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
+ )
+ .keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
+ launchAnimator = launchAnimator,
+ logger = logger,
+ devicePolicyManager = devicePolicyManager,
+ dockManager = dockManager,
+ biometricSettingsRepository = biometricSettingsRepository,
+ backgroundDispatcher = testDispatcher,
+ appContext = mContext,
+ sceneInteractor = { kosmos.sceneInteractor },
+ )
+ }
+
+ @Test
+ fun onQuickAffordanceTriggered() =
+ testScope.runTest {
+ val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ homeControls.setState(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = DRAWABLE,
+ )
+ )
+ homeControls.onTriggeredResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ underTest.onQuickAffordanceTriggered(
+ configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key",
+ expandable = expandable,
+ slotId = "",
+ )
+
+ if (startActivity) {
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
+ } else {
+ verifyZeroInteractions(activityStarter)
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 6b317ea..1881a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -19,6 +19,7 @@
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +33,7 @@
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -75,17 +77,18 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -111,6 +114,10 @@
private val kosmos = testKosmos()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -750,5 +757,11 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1d98dc3..0c98cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -18,11 +18,12 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
@@ -45,14 +46,14 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(JUnit4::class)
-@DisableSceneContainer
-class KeyguardClockViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardClockViewModel
@@ -65,6 +66,10 @@
var config = ClockConfig("TEST", "Test", "")
var faceConfig = ClockFaceConfig()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -276,5 +281,11 @@
companion object {
private const val KEYGUARD_STATUS_BAR_HEIGHT = 20
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index 72fc65b..924b872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -2,6 +2,8 @@
import android.graphics.drawable.Drawable
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.View
import android.view.ViewGroup
@@ -10,10 +12,16 @@
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.message.LabeledIcon
+import com.android.systemui.screenshot.message.ProfileMessageController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,6 +36,7 @@
lateinit var messageContainer: MessageContainerController
@Mock lateinit var workProfileMessageController: WorkProfileMessageController
+ @Mock lateinit var profileMessageController: ProfileMessageController
@Mock lateinit var screenshotDetectionController: ScreenshotDetectionController
@@ -46,12 +55,15 @@
lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
@Before
+ @ExperimentalCoroutinesApi
fun setup() {
MockitoAnnotations.initMocks(this)
messageContainer =
MessageContainerController(
workProfileMessageController,
+ profileMessageController,
screenshotDetectionController,
+ TestScope(UnconfinedTestDispatcher())
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -78,20 +90,11 @@
}
@Test
- fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() {
- // (just being explicit here)
- whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null)
-
- messageContainer.onScreenshotTaken(userHandle)
-
- verify(workProfileMessageController, never()).populateView(any(), any(), any())
- }
-
- @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
.thenReturn(workProfileData)
- messageContainer.onScreenshotTaken(userHandle)
+ messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController)
.populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
@@ -100,10 +103,28 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
+ fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest {
+ val profileData =
+ ProfileMessageController.ProfileFirstRunData(
+ LabeledIcon(appName, icon),
+ ProfileMessageController.FirstRunProfile.PRIVATE
+ )
+ whenever(profileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(profileData)
+ messageContainer.onScreenshotTaken(screenshotData)
+
+ verify(profileMessageController)
+ .bindView(eq(workProfileFirstRunView), eq(profileData), any())
+ assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
+ assertEquals(View.GONE, detectionNoticeView.visibility)
+ }
+
+ @Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
+ verify(profileMessageController, never()).bindView(any(), any(), any())
verify(screenshotDetectionController, never()).populateView(any(), any())
assertEquals(View.GONE, container.visibility)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 4b7d5f0..ebdc49f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -87,7 +87,7 @@
when(mMockContext.getSharedPreferences(
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn(FILES_APP_COMPONENT);
when(mMockContext.getString(R.string.screenshot_default_files_app_name))
.thenReturn(DEFAULT_FILES_APP_LABEL);
@@ -149,7 +149,7 @@
@Test
public void testOnScreenshotTaken_noFilesAppComponentDefined() {
- when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ when(mMockContext.getString(R.string.config_screenshotFilesApp))
.thenReturn("");
WorkProfileMessageController.WorkProfileFirstRunData data =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
new file mode 100644
index 0000000..ce2facd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/message/ProfileMessageControllerTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.message
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class ProfileMessageControllerTest {
+ @Test
+ fun personalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.personalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun communalScreenshot() = runTest {
+ assertThat(
+ getMessageController()
+ .onScreenshotTaken(UserHandle.of(profileTypeRepository.communalUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noUserScreenshot() = runTest {
+ assertThat(getMessageController().onScreenshotTaken(null)).isNull()
+ }
+
+ @Test
+ fun alreadyDismissed() = runTest {
+ val messageController = getMessageController()
+ profileFirstRunSettings.onMessageDismissed(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ )
+ .isNull()
+ }
+
+ @Test
+ fun noFileManager() = runTest {
+ val messageController = getMessageController(fileManagerComponent = null)
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.workUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.WORK)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerNotFound() = runTest {
+ val messageController =
+ getMessageController(fileManagerComponent = ComponentName("Something", "Random"))
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(DEFAULT_APP_NAME)
+ assertThat(data?.labeledIcon?.badgedIcon).isNull()
+ }
+
+ @Test
+ fun fileManagerFound() = runTest {
+ val messageController = getMessageController()
+ val data =
+ messageController.onScreenshotTaken(UserHandle.of(profileTypeRepository.privateUser))
+ assertThat(data?.profileType).isEqualTo(ProfileMessageController.FirstRunProfile.PRIVATE)
+ assertThat(data?.labeledIcon?.label).isEqualTo(FILE_MANAGER_LABEL)
+ assertThat(data?.labeledIcon?.badgedIcon).isEqualTo(drawable)
+ }
+
+ private val drawable =
+ object : Drawable() {
+ override fun draw(canvas: Canvas) {}
+
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ override fun getOpacity(): Int = 0
+ }
+
+ private val packageLabelIconProvider =
+ object : PackageLabelIconProvider {
+ override suspend fun getPackageLabelIcon(
+ componentName: ComponentName,
+ userHandle: UserHandle
+ ): LabeledIcon {
+ if (componentName.equals(FILE_MANAGER_COMPONENT)) {
+ return LabeledIcon(FILE_MANAGER_LABEL, drawable)
+ } else {
+ throw PackageManager.NameNotFoundException()
+ }
+ }
+ }
+
+ private class FakeProfileFirstRunResources(private val fileManager: ComponentName?) :
+ ProfileFirstRunFileResources {
+ override fun fileManagerComponentName(): ComponentName? {
+ return fileManager
+ }
+
+ override fun defaultFileApp() = LabeledIcon(DEFAULT_APP_NAME, badgedIcon = null)
+ }
+
+ private val profileFirstRunSettings =
+ object : ProfileFirstRunSettings {
+ private val dismissed =
+ mutableMapOf(
+ ProfileMessageController.FirstRunProfile.WORK to false,
+ ProfileMessageController.FirstRunProfile.PRIVATE to false,
+ )
+
+ override fun messageAlreadyDismissed(
+ profileType: ProfileMessageController.FirstRunProfile
+ ): Boolean {
+ return dismissed.getOrDefault(profileType, false)
+ }
+
+ override fun onMessageDismissed(profileType: ProfileMessageController.FirstRunProfile) {
+ dismissed[profileType] = true
+ }
+ }
+
+ private val profileTypeRepository =
+ object : ProfileTypeRepository {
+ override suspend fun getProfileType(userId: Int): ProfileType {
+ return when (userId) {
+ workUser -> ProfileType.WORK
+ privateUser -> ProfileType.PRIVATE
+ communalUser -> ProfileType.COMMUNAL
+ else -> ProfileType.NONE
+ }
+ }
+
+ val personalUser = 0
+ val workUser = 1
+ val privateUser = 2
+ val communalUser = 3
+ }
+
+ private fun getMessageController(
+ fileManagerComponent: ComponentName? = FILE_MANAGER_COMPONENT
+ ) =
+ ProfileMessageController(
+ packageLabelIconProvider,
+ FakeProfileFirstRunResources(fileManagerComponent),
+ profileFirstRunSettings,
+ profileTypeRepository
+ )
+
+ companion object {
+ val FILE_MANAGER_COMPONENT = ComponentName("package", "component")
+ const val DEFAULT_APP_NAME = "default app"
+ const val FILE_MANAGER_LABEL = "file manager"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 0a9bac9..7ade053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -49,6 +49,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -58,6 +59,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -469,6 +471,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 4dd97bc..9b4f931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,19 +17,21 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.testing.AndroidTestingRunner
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
@@ -58,15 +60,17 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
- private val shadeRepository = FakeShadeRepository()
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private lateinit var keyguardBypassController: KeyguardBypassController
private lateinit var postureControllerCallback: DevicePostureController.Callback
@@ -79,6 +83,18 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var packageManager: PackageManager
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Captor
private val postureCallbackCaptor =
ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
@@ -148,7 +164,7 @@
statusBarStateController,
lockscreenUserManager,
keyguardStateController,
- shadeRepository,
+ { kosmos.shadeInteractor },
devicePostureController,
keyguardTransitionInteractor,
dumpManager,
@@ -293,13 +309,13 @@
testScope.runTest {
initKeyguardBypassController()
assertThat(keyguardBypassController.qsExpanded).isFalse()
- val job = keyguardBypassController.listenForQsExpandedChange(this)
- shadeRepository.setQsExpansion(0.5f)
+ val job = keyguardBypassController.listenForQsExpandedChange()
+ shadeTestUtil.setQsExpansion(0.5f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isTrue()
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
runCurrent()
assertThat(keyguardBypassController.qsExpanded).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d365663..ba38f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -314,6 +314,7 @@
private class UnfoldConfig : UnfoldTransitionConfig {
override var isEnabled: Boolean = false
override var isHingeAngleEnabled: Boolean = false
+ override val isHapticsEnabled: Boolean = false
override val halfFoldedTimeoutMillis: Int = 0
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index efd7f99..05464f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -23,6 +23,7 @@
import android.app.PendingIntent
import android.app.Person
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.notification.NotificationListenerService.REASON_USER_STOPPED
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -194,7 +195,7 @@
/** Regression test for b/192379214. */
@Test
- @DisableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME)
+ @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
fun onEntryUpdated_notificationWhenIsZero_timeHidden() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(0)
@@ -210,6 +211,22 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
+ fun onEntryUpdated_notificationWhenIsZero_timeShown() {
+ val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
+ notification.modifyNotification(context).setWhen(0)
+
+ notifCollectionListener.onEntryUpdated(notification.build())
+ chipView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ )
+
+ assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ .isGreaterThan(0)
+ }
+
+ @Test
fun onEntryUpdated_notificationWhenIsValid_timeShown() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
notification.modifyNotification(context).setWhen(clock.currentTimeMillis())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 8eea29b..ceaae9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -587,7 +587,7 @@
advanceTimeBy(10.seconds)
assertThat(latest)
- .isEqualTo(context.getString(R.string.satellite_not_connected_carrier_text))
+ .isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index bdd3d18..cfa734a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,71 +16,69 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
-import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
-import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class KeyguardStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardInteractor =
- KeyguardInteractor(
- keyguardRepository,
- mock<CommandQueue>(),
- PowerInteractorFactory.create().powerInteractor,
- FakeKeyguardBouncerRepository(),
- ConfigurationInteractor(FakeConfigurationRepository()),
- FakeShadeRepository(),
- kosmos.keyguardTransitionInteractor,
- { kosmos.sceneInteractor },
- { kosmos.fromGoneTransitionInteractor },
- { kosmos.sharedNotificationContainerInteractor },
- testScope,
- )
- private val keyguardStatusBarInteractor =
- KeyguardStatusBarInteractor(
- FakeKeyguardStatusBarRepository(),
- )
- private val batteryController = mock<BatteryController>()
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor }
+ private val batteryController = kosmos.batteryController
- private val underTest =
- KeyguardStatusBarViewModel(
- testScope.backgroundScope,
- keyguardInteractor,
- keyguardStatusBarInteractor,
- batteryController,
- )
+ lateinit var underTest: KeyguardStatusBarViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest =
+ KeyguardStatusBarViewModel(
+ testScope.backgroundScope,
+ keyguardInteractor,
+ keyguardStatusBarInteractor,
+ batteryController,
+ )
+ }
@Test
fun isVisible_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index fd513c9..06f1a88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -17,13 +17,12 @@
import android.os.VibrationAttributes
import android.os.VibrationEffect
-import android.os.Vibrator
+import android.os.vibrator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,15 +34,20 @@
@SmallTest
class UnfoldHapticsPlayerTest : SysuiTestCase() {
- private val progressProvider = FakeUnfoldTransitionProvider()
- private val vibrator: Vibrator = mock()
- private val testFoldProvider = TestFoldProvider()
+ private val kosmos = testKosmos()
+
+ private val progressProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ private val vibrator = kosmos.vibrator
+ private val transitionConfig = kosmos.unfoldTransitionConfig
+ private val testFoldProvider = kosmos.foldProvider
private lateinit var player: UnfoldHapticsPlayer
@Before
fun before() {
- player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator)
+ transitionConfig.isHapticsEnabled = true
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
}
@Test
@@ -58,6 +62,21 @@
}
@Test
+ fun testHapticsDisabled_unfoldingTransitionFinishing_doesNotPlayHaptics() {
+ transitionConfig.isHapticsEnabled = false
+ player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, transitionConfig,
+ Runnable::run, vibrator)
+
+ testFoldProvider.onFoldUpdate(isFolded = true)
+ testFoldProvider.onFoldUpdate(isFolded = false)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+ progressProvider.onTransitionFinishing()
+
+ verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
+ }
+
+ @Test
fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() {
testFoldProvider.onFoldUpdate(isFolded = true)
testFoldProvider.onFoldUpdate(isFolded = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
new file mode 100644
index 0000000..c073903
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTestUtilsKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.util.TestFoldProvider
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.unfold.config.TestUnfoldTransitionConfig
+
+var Kosmos.foldProvider: TestFoldProvider by Kosmos.Fixture { TestFoldProvider() }
+
+var Kosmos.unfoldTransitionConfig: TestUnfoldTransitionConfig
+ by Kosmos.Fixture { TestUnfoldTransitionConfig() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
index ab450e2..3c53997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
@@ -47,6 +47,12 @@
}
@Test
+ fun testHapticsEnabled() {
+ assertThat(config.isHapticsEnabled).isEqualTo(mContext.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHapticsEnabled))
+ }
+
+ @Test
fun testHalfFoldedTimeout() {
assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
.getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
new file mode 100644
index 0000000..05861150
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/TestUnfoldTransitionConfig.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.config
+
+class TestUnfoldTransitionConfig(
+ override var isEnabled: Boolean = false,
+ override var isHingeAngleEnabled: Boolean = false,
+ override var isHapticsEnabled: Boolean = false,
+ override var halfFoldedTimeoutMillis: Int = 1000
+) : UnfoldTransitionConfig
diff --git a/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt
new file mode 100644
index 0000000..872b25c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/os/VibratorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.vibrator by Kosmos.Fixture { mock<Vibrator>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index e83205c5..1107971 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -21,11 +21,9 @@
import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
-import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
-import com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -36,13 +34,11 @@
FLAG_COMPOSE_LOCKSCREEN,
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
- FLAG_MEDIA_IN_SCENE_CONTAINER,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
FLAG_PREDICTIVE_BACK_SYSUI,
FLAG_SCENE_CONTAINER,
FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
- FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 4221d06..297d1d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.util.mockito.mock
val Kosmos.shadeLockscreenInteractor by
@@ -28,5 +29,6 @@
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
lockIconViewController = mock(),
+ shadeRepository = shadeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
new file mode 100644
index 0000000..da95ee9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { fakeKeyguardStatusBarRepository }
+
+val Kosmos.fakeKeyguardStatusBarRepository: FakeKeyguardStatusBarRepository by
+ Kosmos.Fixture { FakeKeyguardStatusBarRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
new file mode 100644
index 0000000..71ed5f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.keyguardStatusBarRepository
+
+val Kosmos.keyguardStatusBarInteractor: KeyguardStatusBarInteractor by
+ Kosmos.Fixture {
+ KeyguardStatusBarInteractor(
+ keyguardStatusBarRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index cbba80b..d00eedf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
@@ -60,6 +61,7 @@
shadeInteractor = shadeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index 4a79452..146f109 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel
import android.content.res.mainResources
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.fakeConfigurationController
@@ -72,5 +73,6 @@
testScope.backgroundScope,
KosmosVolumePanelComponentFactory(this),
fakeConfigurationController,
+ broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
index c513729..ca1daf6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -33,6 +33,12 @@
Resources.getSystem().getBoolean(id)
}
+ override val isHapticsEnabled: Boolean by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionHapticsEnabled", "bool", "android")
+ Resources.getSystem().getBoolean(id)
+ }
+
override val halfFoldedTimeoutMillis: Int by lazy {
val id = Resources.getSystem()
.getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
index 765e862..1084cb3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -18,5 +18,6 @@
interface UnfoldTransitionConfig {
val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
+ val isHapticsEnabled: Boolean
val halfFoldedTimeoutMillis: Int
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 300b147..8ee560b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -256,6 +256,7 @@
"stats_flags_lib",
"core_os_flags_lib",
"connectivity_flags_lib",
+ "dreams_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c47e42d..1b59c18 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5034,7 +5034,7 @@
}
@Override
- public final void finishAttachApplication(long startSeq) {
+ public final void finishAttachApplication(long startSeq, long timestampApplicationOnCreateNs) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -5054,6 +5054,11 @@
} finally {
Binder.restoreCallingIdentity(origId);
}
+
+ if (android.app.Flags.appStartInfoTimestamps() && timestampApplicationOnCreateNs > 0) {
+ addStartInfoTimestampInternal(ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE,
+ timestampApplicationOnCreateNs, UserHandle.getUserId(uid), uid);
+ }
}
private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) {
@@ -10253,10 +10258,15 @@
mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
ALLOW_NON_FULL, "addStartInfoTimestamp", null);
- final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+ addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
+ }
- mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
- UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
+ private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(
+ Settings.getPackageNameForUid(mContext, uid),
+ UserHandle.getUid(userId, UserHandle.getAppId(uid)),
+ timestampNs,
+ key);
}
@Override
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8b64538..9b83ede 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -35,6 +35,10 @@
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
/**
* Maps system settings to system properties.
@@ -320,15 +324,30 @@
NAMESPACE_REBOOT_STAGING,
AsyncTask.THREAD_POOL_EXECUTOR,
(DeviceConfig.Properties properties) -> {
- String scope = properties.getNamespace();
- for (String key : properties.getKeyset()) {
- String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
- if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/" + key);
- return;
+
+ HashMap<String, HashMap<String, String>> propsToStage =
+ getStagedFlagsWithValueChange(properties);
+
+ for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagValuesToStage = entry.getValue();
+
+ for (String flagName : flagValuesToStage.keySet()) {
+ String stagedValue = flagValuesToStage.get(flagName);
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ actualNamespace, flagName);
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ log("unable to construct system property for " + actualNamespace
+ + "/" + flagName);
+ continue;
+ }
+
+ setProperty(propertyName, stagedValue);
}
- setProperty(aconfigPropertyName, properties.getString(key, null));
}
+
});
}
@@ -401,35 +420,6 @@
}
/**
- * system property name constructing rule for staged aconfig flags, the flag name
- * is in the form of [namespace]*[actual flag name], we should push the following
- * to system properties
- * "next_boot.[actual sys prop name]".
- * If the name contains invalid characters or substrings for system property name,
- * will return null.
- * @param flagName
- * @return
- */
- @VisibleForTesting
- static String makeAconfigFlagStagedPropertyName(String flagName) {
- int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
- if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid staged flag: " + flagName);
- return null;
- }
-
- String propertyName = "next_boot." + makeAconfigFlagPropertyName(
- flagName.substring(0, idx), flagName.substring(idx+1));
-
- if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
- || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- return null;
- }
-
- return propertyName;
- }
-
- /**
* system property name constructing rule for aconfig flags:
* "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
@@ -451,6 +441,63 @@
return propertyName;
}
+ /**
+ * Get the flags that need to be staged in sys prop, only these with a real value
+ * change needs to be staged in sys prop. Otherwise, the flag stage is useless and
+ * create performance problem at sys prop side.
+ * @param properties
+ * @return a hash map of namespace name to actual flags to stage
+ */
+ @VisibleForTesting
+ static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange(
+ DeviceConfig.Properties properties) {
+
+ // sort flags by actual namespace of the flag
+ HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>();
+ for (String flagName : properties.getKeyset()) {
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid staged flag: " + flagName);
+ continue;
+ }
+ String actualNamespace = flagName.substring(0, idx);
+ String actualFlagName = flagName.substring(idx+1);
+ HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace);
+ if (flagStagedValues == null) {
+ flagStagedValues = new HashMap<String, String>();
+ stagedProps.put(actualNamespace, flagStagedValues);
+ }
+ flagStagedValues.put(actualFlagName, properties.getString(flagName, null));
+ }
+
+ // for each namespace, find flags with real flag value change
+ HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>();
+ for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) {
+ String actualNamespace = entry.getKey();
+ HashMap<String, String> flagStagedValues = entry.getValue();
+ Map<String, String> flagCurrentValues = Settings.Config.getStrings(
+ actualNamespace, new ArrayList<String>(flagStagedValues.keySet()));
+
+ HashMap<String, String> flagsToStage = new HashMap<>();
+ for (String flagName : flagStagedValues.keySet()) {
+ String stagedValue = flagStagedValues.get(flagName);
+ String currentValue = flagCurrentValues.get(flagName);
+ if (currentValue == null) {
+ currentValue = new String("false");
+ }
+ if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) {
+ flagsToStage.put(flagName, stagedValue);
+ }
+ }
+
+ if (!flagsToStage.isEmpty()) {
+ propsToStage.put(actualNamespace, flagsToStage);
+ }
+ }
+
+ return propsToStage;
+ }
+
private void setProperty(String key, String value) {
// Check if need to clear the property
if (value == null) {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 94baf88..2285826 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -134,7 +135,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -855,7 +856,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
}
if (recycled != null) {
@@ -881,10 +882,11 @@
AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
@Nullable String packageName,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag,
+ @Nullable String deviceId) {
AppOpsManager.OpEventProxyInfo recycled = acquire();
if (recycled != null) {
- recycled.reinit(uid, packageName, attributionTag);
+ recycled.reinit(uid, packageName, attributionTag, deviceId);
return recycled;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 559462a..b0e7575 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -218,6 +218,7 @@
}
@VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index b7e3f70..1c6dfe0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -242,6 +242,7 @@
}
@Nullable protected AidlSession getSessionForUser(int userId) {
+ Slog.d(TAG, "getSessionForUser: mCurrentSession: " + mCurrentSession);
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 68e2bd6..7106e89 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2764,21 +2764,9 @@
display.setHasContentLocked(hasContent);
shouldScheduleTraversal = true;
}
- if (requestedModeId == 0 && requestedRefreshRate != 0) {
- // Scan supported modes returned by display.getInfo() to find a mode with the same
- // size as the default display mode but with the specified refresh rate instead.
- Display.Mode mode = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
- requestedRefreshRate);
- if (mode != null) {
- requestedModeId = mode.getModeId();
- } else {
- Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
- + requestedRefreshRate + " on Display: " + displayId);
- }
- }
- mDisplayModeDirector.getAppRequestObserver().setAppRequest(
- displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
+ mDisplayModeDirector.getAppRequestObserver().setAppRequest(displayId, requestedModeId,
+ requestedRefreshRate, requestedMinRefreshRate, requestedMaxRefreshRate);
// TODO(b/202378408) set minimal post-processing only if it's supported once we have a
// separate API for disabling on-device processing.
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index d3de71e..cd07f5a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -159,6 +159,11 @@
Flags::enablePeakRefreshRatePhysicalLimit
);
+ private final FlagState mIgnoreAppPreferredRefreshRate = new FlagState(
+ Flags.FLAG_IGNORE_APP_PREFERRED_REFRESH_RATE_REQUEST,
+ Flags::ignoreAppPreferredRefreshRateRequest
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -322,6 +327,13 @@
}
/**
+ * @return Whether to ignore preferredRefreshRate app request or not
+ */
+ public boolean ignoreAppPreferredRefreshRateRequest() {
+ return mIgnoreAppPreferredRefreshRate.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3d64fc2..a15a8e8 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -255,3 +255,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ignore_app_preferred_refresh_rate_request"
+ namespace: "display_manager"
+ description: "Feature flag for DisplayManager to ignore preferred refresh rate app request. It will be handled by SF only."
+ bug: "330810426"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 3084dae..91bd80e 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -222,7 +222,7 @@
displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
- mAppRequestObserver = new AppRequestObserver();
+ mAppRequestObserver = new AppRequestObserver(displayManagerFlags);
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler, displayManagerFlags);
@@ -1205,17 +1205,32 @@
public final class AppRequestObserver {
private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
+ private final boolean mIgnorePreferredRefreshRate;
- AppRequestObserver() {
+ AppRequestObserver(DisplayManagerFlags flags) {
mAppRequestedModeByDisplay = new SparseArray<>();
mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
+ mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
}
/**
* Sets refresh rates from app request
*/
- public void setAppRequest(int displayId, int modeId,
+ public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+
+ if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+ // Scan supported modes returned to find a mode with the same
+ // size as the default display mode but with the specified refresh rate instead.
+ Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
+ if (mode != null) {
+ modeId = mode.getModeId();
+ } else {
+ Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
+ + requestedRefreshRate + " on Display: " + displayId);
+ }
+ }
+
synchronized (mLock) {
setAppRequestedModeLocked(displayId, modeId);
setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
@@ -1223,6 +1238,23 @@
}
}
+ @Nullable
+ private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
+ Display.Mode[] modes;
+ Display.Mode defaultMode;
+ synchronized (mLock) {
+ modes = mSupportedModesByDisplay.get(displayId);
+ defaultMode = mDefaultModeByDisplay.get(displayId);
+ }
+ for (int i = 0; i < modes.length; i++) {
+ if (modes[i].matches(defaultMode.getPhysicalWidth(),
+ defaultMode.getPhysicalHeight(), refreshRate)) {
+ return modes[i];
+ }
+ }
+ return null;
+ }
+
private void setAppRequestedModeLocked(int displayId, int modeId) {
final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
diff --git a/services/core/java/com/android/server/dreams/Android.bp b/services/core/java/com/android/server/dreams/Android.bp
new file mode 100644
index 0000000..4078a42
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "dreams_flags",
+ package: "com.android.server.dreams",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "dreams_flags_lib",
+ aconfig_declarations: "dreams_flags",
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fc63494..2def5ae 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -45,6 +45,7 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -118,6 +119,7 @@
private final DreamController mController;
private final PowerManager mPowerManager;
private final PowerManagerInternal mPowerManagerInternal;
+ private final BatteryManagerInternal mBatteryManagerInternal;
private final PowerManager.WakeLock mDozeWakeLock;
private final ActivityTaskManagerInternal mAtmInternal;
private final PackageManagerInternal mPmInternal;
@@ -186,7 +188,11 @@
private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ if (Flags.useBatteryChangedBroadcast()) {
+ mIsCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ } else {
+ mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ }
}
};
@@ -251,6 +257,12 @@
com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
+
+ if (Flags.useBatteryChangedBroadcast()) {
+ mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ } else {
+ mBatteryManagerInternal = null;
+ }
}
@Override
@@ -279,9 +291,15 @@
mContext.registerReceiver(
mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+
IntentFilter chargingIntentFilter = new IntentFilter();
- chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
- chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ if (Flags.useBatteryChangedBroadcast()) {
+ chargingIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ chargingIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ } else {
+ chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+ chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ }
mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
mSettingsObserver = new SettingsObserver(mHandler);
diff --git a/services/core/java/com/android/server/dreams/flags.aconfig b/services/core/java/com/android/server/dreams/flags.aconfig
new file mode 100644
index 0000000..5d35ebd
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.dreams"
+container: "system"
+
+flag {
+ name: "use_battery_changed_broadcast"
+ namespace: "communal"
+ description: "Use ACTION_BATTERY_CHANGED broadcast to track charging state"
+ bug: "329125239"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 3bd0a9f..326ef7e0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -98,7 +98,10 @@
final int size = imList.size();
mIms = new InputMethodInfo[size];
mSubtypeIds = new int[size];
- int checkedItem = 0;
+ // No items are checked by default. When we have a list of explicitly enabled subtypes,
+ // the implicit subtype is no longer listed, but if it is still the selected one,
+ // no items will be shown as checked.
+ int checkedItem = -1;
for (int i = 0; i < size; ++i) {
final ImeSubtypeListItem item = imList.get(i);
mIms[i] = item.mImi;
@@ -113,6 +116,12 @@
}
}
+ if (checkedItem == -1) {
+ Slog.w(TAG, "Switching menu shown with no item selected"
+ + ", IME id: " + preferredInputMethodId
+ + ", subtype index: " + preferredInputMethodSubtypeId);
+ }
+
if (mDialogWindowContext == null) {
mDialogWindowContext = new InputMethodDialogWindowContext();
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 96f32f3..bf49671 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -497,7 +497,7 @@
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
if (buzz || beep) {
record.resetRankingTime();
}
@@ -1528,7 +1528,7 @@
// recent conversation
if (record.isConversation()
- && record.getNotification().when > mLastAvalancheTriggerTimestamp) {
+ && record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9d4ab11..ca6ae63 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.Flags.sortSectionByTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
@@ -8593,7 +8594,7 @@
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
if (isInterruptive) {
r.resetRankingTime();
}
@@ -8738,7 +8739,7 @@
return false;
}
- if (android.app.Flags.updateRankingTime()) {
+ if (sortSectionByTime()) {
// Ignore visual interruptions from FGS/UIJs because users
// consider them one 'session'. Count them for everything else.
if (r.getSbn().getNotification().isFgsOrUij()) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 38c95f7..0c6a6c8 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -18,7 +18,7 @@
import static android.app.Flags.restrictAudioAttributesAlarm;
import static android.app.Flags.restrictAudioAttributesCall;
import static android.app.Flags.restrictAudioAttributesMedia;
-import static android.app.Flags.updateRankingTime;
+import static android.app.Flags.sortSectionByTime;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -580,7 +580,7 @@
pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
pw.println(prefix + "number=" + notification.number);
pw.println(prefix + "groupAlertBehavior=" + notification.getGroupAlertBehavior());
- pw.println(prefix + "when=" + notification.when);
+ pw.println(prefix + "when=" + notification.when + "/" + notification.getWhen());
pw.print(prefix + "tickerText=");
if (!TextUtils.isEmpty(notification.tickerText)) {
@@ -1092,9 +1092,9 @@
private long calculateRankingTimeMs(long previousRankingTimeMs) {
Notification n = getNotification();
// Take developer provided 'when', unless it's in the future.
- if (updateRankingTime()) {
- if (n.hasAppProvidedWhen() && n.when <= getSbn().getPostTime()){
- return n.when;
+ if (sortSectionByTime()) {
+ if (n.hasAppProvidedWhen() && n.getWhen() <= getSbn().getPostTime()){
+ return n.getWhen();
}
} else {
if (n.when != 0 && n.when <= getSbn().getPostTime()) {
@@ -1211,7 +1211,7 @@
}
public void resetRankingTime() {
- if (updateRankingTime()) {
+ if (sortSectionByTime()) {
mRankingTimeMs = calculateRankingTimeMs(getSbn().getPostTime());
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9a6ea2c2..65ef53f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -542,7 +542,7 @@
this.is_locked = p.r.isLocked();
this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes(
- p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().when);
+ p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen());
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1f2ad07e..309e945 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -26,6 +26,7 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -139,6 +140,8 @@
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+
+ private static final String ATT_USERID = "userid";
private static final String ATT_ID = "id";
private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String ATT_PRIORITY = "priority";
@@ -268,7 +271,7 @@
}
if (type == XmlPullParser.START_TAG) {
if (TAG_STATUS_ICONS.equals(tag)) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mHideSilentStatusBarIcons = parser.getAttributeBoolean(null,
@@ -311,8 +314,16 @@
: parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ // when data is loaded from disk it's loaded as USER_ALL, but restored data that
+ // is pending app install needs the user id that the data was restored to
+ int fixedUserId = userId;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (!forRestore && uid == UNKNOWN_UID) {
+ fixedUserId = parser.getAttributeInt(null, ATT_USERID, USER_SYSTEM);
+ }
+ }
PackagePreferences r = getOrCreatePackagePreferencesLocked(
- name, userId, uid,
+ name, fixedUserId, uid,
appImportance,
parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
@@ -504,6 +515,9 @@
}
if (r.uid == UNKNOWN_UID) {
+ if (Flags.persistIncompleteRestoreData()) {
+ r.userId = userId;
+ }
mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
} else {
mPackagePreferences.put(key, r);
@@ -674,6 +688,7 @@
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+ out.attributeInt(null, ATT_USERID, r.userId);
}
if (!forBackup) {
@@ -2947,6 +2962,8 @@
boolean migrateToPm = false;
long creationTime;
+ @UserIdInt int userId;
+
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7756801..03dd935 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -147,11 +147,9 @@
if (sortSectionByTime()) {
final String groupKey = record.getGroupKey();
NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
- // summaries are mostly hidden in systemui - if there is a child notification
- // with better information, use its rank
- if (existingProxy == null
- || (existingProxy.getNotification().isGroupSummary()
- && !existingProxy.getNotification().hasAppProvidedWhen())) {
+ // summaries are mostly hidden in systemui - if there is a child notification,
+ // use its rank
+ if (existingProxy == null || existingProxy.getNotification().isGroupSummary()) {
mProxyByGroupTmp.put(groupKey, record);
}
} else {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b2c6c49..47ee1d0 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -513,7 +513,11 @@
// Legacy behavior to report appId as UID here.
// The final broadcasts will contain a per-user UID.
outInfo.mUid = ps.getAppId();
- outInfo.mIsAppIdRemoved = true;
+ // Only send Intent.ACTION_UID_REMOVED when flag & DELETE_KEEP_DATA is 0
+ // i.e. the mDataRemoved is true
+ if (outInfo.mDataRemoved) {
+ outInfo.mIsAppIdRemoved = true;
+ }
mPm.scheduleWritePackageRestrictions(user);
return;
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 2e67b2f..9ab6016 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -170,6 +170,7 @@
}
}
+ boolean isPendingRestoreBefore = false;
if (pkgSetting != null && oldSharedUserSetting != sharedUserSetting) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Package " + parsedPackage.getPackageName() + " shared user changed from "
@@ -178,6 +179,9 @@
+ " to "
+ (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
+ "; replacing with new");
+ // Preserve the value of isPendingRestore. We need to set it to the new PackageSetting
+ // if the value is true to restore the app
+ isPendingRestoreBefore = pkgSetting.isPendingRestore();
pkgSetting = null;
}
@@ -224,6 +228,11 @@
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
+
+ // If isPendingRestore is true before, set the value true to the PackageSetting
+ if (isPendingRestoreBefore) {
+ pkgSetting.setPendingRestore(true);
+ }
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 5e24673..00582bf 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -46,7 +46,7 @@
/**
* Launcher information used by {@link ShortcutService}.
*
- * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ * All methods should be guarded by {@code ShortcutPackageItem#mPackageItemLock}.
*/
class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -66,7 +66,7 @@
/**
* Package name -> IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
@@ -99,7 +99,7 @@
*/
private void onRestoreBlocked() {
final ArrayList<UserPackage> pinnedPackages;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
}
@@ -138,7 +138,7 @@
final int idSize = ids.size();
if (idSize == 0) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mPinnedShortcuts.remove(up);
}
} else {
@@ -165,7 +165,7 @@
floatingSet.add(id);
}
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
if (prevSet != null) {
for (String id : floatingSet) {
@@ -187,7 +187,7 @@
@Nullable
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
@UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedShortcuts = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
return pinnedShortcuts == null ? null : new ArraySet<>(pinnedShortcuts);
@@ -198,7 +198,7 @@
* Return true if the given shortcut is pinned by this launcher.<code></code>
*/
public boolean hasPinned(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinned = mPinnedShortcuts.get(
UserPackage.of(shortcut.getUserId(), shortcut.getPackage()));
return (pinned != null) && pinned.contains(shortcut.getId());
@@ -211,7 +211,7 @@
public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
String id, boolean forPinRequest) {
final ArrayList<String> pinnedList;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final ArraySet<String> pinnedSet = mPinnedShortcuts.get(
UserPackage.of(packageUserId, packageName));
if (pinnedSet != null) {
@@ -227,7 +227,7 @@
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
}
}
@@ -253,7 +253,7 @@
return;
}
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
@@ -366,7 +366,7 @@
: ShortcutService.parseIntAttribute(parser,
ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mPinnedShortcuts.put(
UserPackage.of(packageUserId, packageName), ids);
}
@@ -407,7 +407,7 @@
pw.println();
final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts);
}
final int size = pinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 449e9ab..c929c1f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -163,20 +163,20 @@
/**
* An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
* IDs.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
/**
* All the share targets from the package
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
/**
@@ -193,10 +193,10 @@
private long mLastKnownForegroundElapsedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private long mLastReportedTime;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -233,7 +233,7 @@
}
public int getShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.size();
}
}
@@ -276,7 +276,7 @@
@Nullable
public ShortcutInfo findShortcutById(@Nullable final String id) {
if (id == null) return null;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcuts.get(id);
}
}
@@ -354,7 +354,7 @@
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
shortcut = mShortcuts.remove(id);
if (shortcut != null) {
removeIcon(shortcut);
@@ -409,7 +409,7 @@
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -482,7 +482,7 @@
if (newShortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)) {
if (isAppSearchEnabled()) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mTransientShortcuts.put(newShortcut.getId(), newShortcut);
}
}
@@ -506,7 +506,7 @@
final ShortcutService service = mShortcutUser.mService;
// Ensure the total number of shortcuts doesn't exceed the hard limit per app.
final int maxShortcutPerApp = service.getMaxAppShortcuts();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
!si.isPinned()).collect(Collectors.toList());
if (appShortcuts.size() >= maxShortcutPerApp) {
@@ -555,7 +555,7 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic() && si.isVisibleToPublisher()) {
@@ -914,7 +914,7 @@
List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
@NonNull final IntentFilter filter, @Nullable final String pkgName) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
final ShareTargetInfo target = mShareTargets.get(i);
@@ -967,7 +967,7 @@
}
public boolean hasShareTargets() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return !mShareTargets.isEmpty();
}
}
@@ -978,7 +978,7 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (mShareTargets.isEmpty()) {
return 0;
}
@@ -1017,7 +1017,7 @@
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
private ArraySet<String> getUsedBitmapFilesLocked() {
final ArraySet<String> usedFiles = new ArraySet<>(1);
forEachShortcut(si -> {
@@ -1029,7 +1029,7 @@
}
public void cleanupDanglingBitmapFiles(@NonNull File path) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.waitForAllSavesLocked();
final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
@@ -1136,7 +1136,7 @@
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
int shareTargetSize = 0;
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
try {
shareTargetSize = mShareTargets.size();
newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
@@ -1680,7 +1680,7 @@
void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
@NonNull final String shortcutId) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final long currentTS = SystemClock.elapsedRealtime();
final ShortcutService s = mShortcutUser.mService;
if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
@@ -1757,7 +1757,7 @@
pw.println(")");
pw.println();
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.dumpLocked(pw, " ");
}
}
@@ -1827,7 +1827,7 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
final int size = mShortcuts.size();
final int shareTargetSize = mShareTargets.size();
@@ -2037,7 +2037,7 @@
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
- synchronized (ret.mLock) {
+ synchronized (ret.mPackageItemLock) {
ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute(
parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION;
@@ -2283,7 +2283,7 @@
@VisibleForTesting
List<ShareTargetInfo> getAllShareTargetsForTest() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return new ArrayList<>(mShareTargets);
}
}
@@ -2404,7 +2404,7 @@
@NonNull final Consumer<ShortcutInfo> transform) {
Objects.requireNonNull(id);
Objects.requireNonNull(transform);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (shortcut != null) {
transform.accept(shortcut);
}
@@ -2424,7 +2424,7 @@
private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (ShortcutInfo si : shortcuts) {
mShortcuts.put(si.getId(), si);
}
@@ -2433,7 +2433,7 @@
@Nullable
List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return ids.stream().map(mShortcuts::get)
.filter(Objects::nonNull).collect(Collectors.toList());
}
@@ -2455,7 +2455,7 @@
private void forEachShortcutStopWhen(
@NonNull final Function<ShortcutInfo, Boolean> cb) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (cb.apply(si)) {
@@ -2600,7 +2600,7 @@
})));
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
@Override
void scheduleSaveToAppSearchLocked() {
final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts);
@@ -2684,7 +2684,7 @@
.penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
.build());
future = mShortcutUser.getAppSearch(searchContext);
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
if (!mIsAppSearchSchemaUpToDate) {
future = future.thenCompose(this::setupSchema);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 12115af..dfd2e08 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -39,7 +39,7 @@
import java.util.Objects;
/**
- * All methods should be either guarded by {@code #mShortcutUser.mService.mLock} or {@code #mLock}.
+ * All methods should be either guarded by {@code #mPackageItemLock}.
*/
abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -52,10 +52,10 @@
protected ShortcutUser mShortcutUser;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
protected final ShortcutBitmapSaver mShortcutBitmapSaver;
- protected final Object mLock = new Object();
+ protected final Object mPackageItemLock = new Object();
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
int packageUserId, @NonNull String packageName,
@@ -157,7 +157,7 @@
public abstract void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
public void saveToFileLocked(File path, boolean forBackup) {
try (ResilientAtomicFile file = getResilientFile(path)) {
FileOutputStream os = null;
@@ -187,7 +187,7 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mPackageItemLock")
void scheduleSaveToAppSearchLocked() {
}
@@ -219,7 +219,7 @@
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
Slog.d(TAG, "Saving package item " + getPackageName() + " to " + path);
}
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
path.getParentFile().mkdirs();
// TODO: Since we are persisting shortcuts into AppSearch, we should read from/write to
// AppSearch as opposed to maintaining a separate XML file.
@@ -229,14 +229,14 @@
}
public boolean waitForBitmapSaves() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.waitForAllSavesLocked();
}
}
public void saveBitmap(ShortcutInfo shortcut,
int maxDimension, Bitmap.CompressFormat format, int quality) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
}
}
@@ -246,19 +246,19 @@
*/
@Nullable
public String getBitmapPathMayWait(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
}
}
public void removeIcon(ShortcutInfo shortcut) {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
mShortcutBitmapSaver.removeIcon(shortcut);
}
}
void removeShortcutPackageItem() {
- synchronized (mLock) {
+ synchronized (mPackageItemLock) {
getResilientFile(getShortcutPackageItemFile()).delete();
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fe9c3f2..3f5ec06 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -295,7 +295,7 @@
final Context mContext;
- private final Object mLock = new Object();
+ private final Object mServiceLock = new Object();
private final Object mNonPersistentUsersLock = new Object();
private final Object mWtfLock = new Object();
@@ -333,7 +333,7 @@
/**
* User ID -> UserShortcuts
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
/**
@@ -388,13 +388,13 @@
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
private final ShortcutDumpFiles mShortcutDumpFiles;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseIntArray mUidState = new SparseIntArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private List<Integer> mDirtyUserIds = new ArrayList<>();
private final AtomicBoolean mBootCompleted = new AtomicBoolean();
@@ -473,7 +473,7 @@
@GuardedBy("mWtfLock")
private Exception mLastWtfStacktrace;
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final boolean mIsAppSearchEnabled;
@@ -518,7 +518,7 @@
mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG);
mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
- mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
+ mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mServiceLock);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false)
@@ -595,7 +595,7 @@
// Default launcher is removed or changed, revoke all URI permissions.
mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner, null, ~0, 0);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
// Clear the launcher cache for this user. It will be set again next time the default
// launcher is read from RoleManager.
if (isUserLoadedLocked(userId)) {
@@ -622,7 +622,7 @@
Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleOnUidStateChanged");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mUidState.put(uid, procState);
// We need to keep track of last time an app comes to foreground.
@@ -639,7 +639,7 @@
return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
boolean isUidForegroundLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
// IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
@@ -655,7 +655,7 @@
return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getUidLastForegroundElapsedTimeLocked(int uid) {
return mUidLastForegroundElapsedTime.get(uid);
}
@@ -729,7 +729,7 @@
final long start = getStatStartTime();
injectRunOnNewThread(() -> {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleUnlockUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start);
getUserShortcutsLocked(userId);
}
@@ -743,7 +743,7 @@
Slog.d(TAG, "handleStopUser: user=" + userId);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleStopUser");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
unloadUserLocked(userId);
synchronized (mUnlockedUsers) {
@@ -753,7 +753,7 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void unloadUserLocked(int userId) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "unloadUserLocked: user=" + userId);
@@ -784,7 +784,7 @@
* Init the instance. (load the state file, etc)
*/
private void initialize() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
loadBaseStateLocked();
}
@@ -1003,7 +1003,7 @@
FileOutputStream outs = null;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
outs = file.startWrite();
}
@@ -1029,7 +1029,7 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void loadBaseStateLocked() {
mRawLastResetTime.set(0);
@@ -1104,7 +1104,7 @@
Slog.d(TAG, "Saving to " + file);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
os = file.startWrite();
saveUserInternalLocked(userId, os, /* forBackup= */ false);
}
@@ -1122,7 +1122,7 @@
getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
boolean forBackup) throws IOException, XmlPullParserException {
@@ -1224,7 +1224,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Scheduling to save for " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!mDirtyUserIds.contains(userId)) {
mDirtyUserIds.add(userId);
}
@@ -1245,7 +1245,7 @@
try {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo");
List<Integer> dirtyUserIds = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
List<Integer> tmp = mDirtyUserIds;
mDirtyUserIds = dirtyUserIds;
dirtyUserIds = tmp;
@@ -1266,14 +1266,14 @@
}
/** Return the last reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getLastResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get();
}
/** Return the next reset time. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
long getNextResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime.get() + mResetInterval;
@@ -1286,7 +1286,7 @@
/**
* Update the last reset time.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void updateTimesLocked() {
final long now = injectCurrentTimeMillis();
@@ -1315,7 +1315,7 @@
}
}
- // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
protected boolean isUserUnlockedL(@UserIdInt int userId) {
// First, check the local copy.
synchronized (mUnlockedUsers) {
@@ -1331,14 +1331,14 @@
return mUserManagerInternal.isUserUnlockingOrUnlocked(userId);
}
- // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
+ // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
void throwIfUserLockedL(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
throw new IllegalStateException("User " + userId + " is locked or not running");
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
@@ -1347,7 +1347,7 @@
private int mLastLockedUser = -1;
/** Return the per-user state. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
if (!isUserUnlockedL(userId)) {
@@ -1386,7 +1386,7 @@
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
for (int i = mUsers.size() - 1; i >= 0; i--) {
c.accept(mUsers.valueAt(i));
@@ -1397,7 +1397,7 @@
* Return the per-user per-package state. If the caller is a publisher, use
* {@link #getPackageShortcutsForPublisherLocked} instead.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1405,7 +1405,7 @@
}
/** Return the per-user per-package state. Use this when the caller is a publisher. */
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutPackage getPackageShortcutsForPublisherLocked(
@NonNull String packageName, @UserIdInt int userId) {
@@ -1414,7 +1414,7 @@
return ret;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@NonNull
ShortcutLauncher getLauncherShortcutsLocked(
@NonNull String packageName, @UserIdInt int ownerUserId,
@@ -1443,7 +1443,7 @@
* {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
* saves are going on.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
@@ -1780,7 +1780,7 @@
void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) {
Objects.requireNonNull(token);
Objects.requireNonNull(r);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
mHandler.removeCallbacksAndMessages(token);
mHandler.postDelayed(r, token, CALLBACK_DELAY);
}
@@ -2015,7 +2015,7 @@
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2084,7 +2084,7 @@
final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2184,7 +2184,7 @@
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2241,7 +2241,7 @@
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2306,7 +2306,7 @@
verifyCaller(packageName, userId);
verifyShortcutInfoPackage(packageName, shortcut);
final Intent intent;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
// Send request to the launcher, if supported.
intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
@@ -2337,7 +2337,7 @@
}
final boolean ret;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
Preconditions.checkState(isUidForegroundLocked(callingUid),
@@ -2378,7 +2378,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2419,7 +2419,7 @@
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
List<ShortcutInfo> changedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2449,7 +2449,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2487,7 +2487,7 @@
List<ShortcutInfo> changedShortcuts = new ArrayList<>();
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
// Dynamic shortcuts that are either cached or pinned will not get deleted.
@@ -2511,7 +2511,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
@@ -2545,7 +2545,7 @@
public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName,
@ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0;
final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0;
@@ -2575,7 +2575,7 @@
"getShareTargets");
final ComponentName chooser = injectChooserActivity();
final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2592,7 +2592,7 @@
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"hasShareTargets");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
@@ -2606,7 +2606,7 @@
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"isSharingShortcut");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(callingUserId);
@@ -2623,7 +2623,7 @@
return false;
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) {
@@ -2649,7 +2649,7 @@
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
@@ -2661,7 +2661,7 @@
public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
return getNextResetTimeLocked();
@@ -2672,7 +2672,7 @@
public int getIconMaxDimensions(String packageName, int userId) {
verifyCaller(packageName, userId);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
return mMaxIconDimension;
}
}
@@ -2686,7 +2686,7 @@
shortcutId, packageName, userId));
}
final ShortcutPackage ps;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
@@ -2723,7 +2723,7 @@
}
void resetThrottlingInner(@UserIdInt int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
Log.w(TAG, "User " + userId + " is locked or not running");
return;
@@ -2747,7 +2747,7 @@
Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId);
}
enforceResetThrottlingPermission();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
// This is called by system UI, so no need to throw. Just ignore.
return;
@@ -2804,7 +2804,7 @@
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final String defaultLauncher = getDefaultLauncher(userId);
@@ -2830,7 +2830,7 @@
final long token = injectClearCallingIdentity();
boolean isSupported;
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
isSupported = !mUserManagerInternal.getUserProperties(userId)
.areItemsRestrictedOnHomeScreen();
}
@@ -2846,7 +2846,7 @@
final long start = getStatStartTime();
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -2890,7 +2890,7 @@
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
boolean appStillExists) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(user ->
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
appStillExists));
@@ -2904,7 +2904,7 @@
*
* This is called when an app is uninstalled, or an app gets "clear data"ed.
*/
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
boolean appStillExists) {
@@ -2979,7 +2979,7 @@
shortcutIds = null; // LauncherAppsService already threw for it though.
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3005,7 +3005,7 @@
return setReturnedByServer(ret);
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable List<LocusId> locusIds, long changedSince,
@@ -3095,7 +3095,7 @@
return;
}
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3129,7 +3129,7 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3143,7 +3143,7 @@
}
}
- @GuardedBy("ShortcutService.this.mLock")
+ @GuardedBy("ShortcutService.this.mServiceLock")
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
@@ -3176,7 +3176,7 @@
throwIfUserLockedL(launcherUserId);
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
}
if (p == null) {
@@ -3198,7 +3198,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3284,7 +3284,7 @@
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
final ShortcutPackage sp;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3346,7 +3346,7 @@
Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3380,7 +3380,7 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
// Check in memory shortcut first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3430,7 +3430,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3458,7 +3458,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3484,7 +3484,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3515,7 +3515,7 @@
// Checks shortcuts in memory first
final ShortcutPackage p;
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3568,7 +3568,7 @@
Objects.requireNonNull(packageName, "packageName");
Objects.requireNonNull(shortcutId, "shortcutId");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3599,7 +3599,7 @@
Objects.requireNonNull(shortcutId, "shortcutId");
// Checks shortcuts in memory first
- synchronized (mLock) {
+ synchronized (mServiceLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3702,7 +3702,7 @@
if (!callingPackage.equals(defaultLauncher)) {
return false;
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUidForegroundLocked(callingUid)) {
return false;
}
@@ -3733,7 +3733,7 @@
}
scheduleSaveBaseState();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final long token = injectClearCallingIdentity();
try {
forEachLoadedUserLocked(user -> user.detectLocaleChange());
@@ -3762,7 +3762,7 @@
// but we still check it in unit tests.
final long token = injectClearCallingIdentity();
try {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
if (DEBUG) {
Slog.d(TAG, "Ignoring package broadcast " + action
@@ -3821,7 +3821,7 @@
// Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
// in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
// We need it so that it can finish up saving before shutdown.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
mHandler.removeCallbacks(mSaveDirtyInfoRunner);
forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks);
@@ -3852,7 +3852,7 @@
try {
final ArrayList<UserPackage> gonePackages = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
// Find packages that have been uninstalled.
@@ -3885,7 +3885,7 @@
verifyStates();
}
- @GuardedBy("mLock")
+ @GuardedBy("mServiceLock")
private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
if (DEBUG_REBOOT) {
Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime);
@@ -3916,7 +3916,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -3929,7 +3929,7 @@
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
@@ -3972,7 +3972,7 @@
}
// Activities may be disabled or enabled. Just rescan the package.
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
@@ -4474,7 +4474,7 @@
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't backup: user " + userId + " is locked or not running");
return null;
@@ -4524,7 +4524,7 @@
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Restoring user " + userId);
}
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!isUserUnlockedL(userId)) {
wtf("Can't restore: user " + userId + " is locked or not running");
return;
@@ -4762,7 +4762,7 @@
}
private void dumpInner(PrintWriter pw, DumpFilter filter) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (filter.shouldDumpDetails()) {
final long now = injectCurrentTimeMillis();
pw.print("Now: [");
@@ -4841,7 +4841,7 @@
}
private void dumpUid(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)");
for (int i = 0; i < mUidState.size(); i++) {
@@ -4876,7 +4876,7 @@
* behavior but shortcut service doesn't for now.
*/
private void dumpCheckin(PrintWriter pw, boolean clear) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
try {
final JSONArray users = new JSONArray();
@@ -4898,7 +4898,7 @@
}
private void dumpDumpFiles(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)");
mShortcutDumpFiles.dumpAll(pw);
}
@@ -5051,7 +5051,7 @@
}
private void handleResetThrottling() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
@@ -5071,7 +5071,7 @@
Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
- synchronized (mLock) {
+ synchronized (mServiceLock) {
if (!updateConfigurationLocked(config)) {
throw new CommandException("override-config failed. See logcat for details.");
}
@@ -5081,7 +5081,7 @@
private void handleResetConfig() {
Slog.i(TAG, "cmd: handleResetConfig");
- synchronized (mLock) {
+ synchronized (mServiceLock) {
loadConfigurationLocked();
}
}
@@ -5090,7 +5090,7 @@
// should query this information directly from RoleManager instead. Keeping the old behavior
// by returning the result from package manager.
private void handleGetDefaultLauncher() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String defaultLauncher = getDefaultLauncher(mUserId);
@@ -5114,7 +5114,7 @@
}
private void handleUnloadUser() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
@@ -5124,7 +5124,7 @@
}
private void handleClearShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5136,7 +5136,7 @@
}
private void handleGetShortcuts() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5162,7 +5162,7 @@
}
private void handleHasShortcutAccess() throws CommandException {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
parseOptionsLocked(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
@@ -5318,7 +5318,7 @@
@VisibleForTesting
ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5328,7 +5328,7 @@
@VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return null;
@@ -5339,7 +5339,7 @@
@VisibleForTesting
void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
Consumer<ShortcutInfo> cb) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return;
cb.accept(pkg.findShortcutById(shortcutId));
@@ -5348,7 +5348,7 @@
@VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
@@ -5385,14 +5385,14 @@
}
private void verifyStatesInner() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
}
}
@VisibleForTesting
void waitForBitmapSavesForTest() {
- synchronized (mLock) {
+ synchronized (mServiceLock) {
forEachLoadedUserLocked(u ->
u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 98499417..deaa8d8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -494,6 +494,7 @@
PhoneCarrierPrivilegesCallback(int phoneId) {
mPhoneId = phoneId;
}
+
@Override
public void onCarrierPrivilegesChanged(
@NonNull Set<String> privilegedPackageNames,
@@ -563,7 +564,11 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("Permission_grant_default_permissions-" + userId);
- grantOrUpgradeDefaultRuntimePermissionsIfNeeded(userId);
+ if (mPackageManagerInternal.isPermissionUpgradeNeeded(userId)) {
+ grantOrUpgradeDefaultRuntimePermissions(userId);
+ updateUserSensitive(userId);
+ mPackageManagerInternal.updateRuntimePermissionsFingerprint(userId);
+ }
t.traceEnd();
final OnInitializedCallback callback;
@@ -595,59 +600,56 @@
}
}
- private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) {
+ private void grantOrUpgradeDefaultRuntimePermissions(@UserIdInt int userId) {
if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
return;
}
- if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")");
+ if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPerms(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- final PackageManagerInternal packageManagerInternal =
- LocalServices.getService(PackageManagerInternal.class);
- final PermissionManagerServiceInternal permissionManagerInternal =
- LocalServices.getService(PermissionManagerServiceInternal.class);
- if (packageManagerInternal.isPermissionUpgradeNeeded(userId)) {
- if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
+ // Now call into the permission controller to apply policy around permissions
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
- // Now call into the permission controller to apply policy around permissions
- final AndroidFuture<Boolean> future = new AndroidFuture<>();
-
- // We need to create a local manager that does not schedule work on the main
- // there as we are on the main thread and want to block until the work is
- // completed or we time out.
- final PermissionControllerManager permissionControllerManager =
- new PermissionControllerManager(
- getUserContext(getContext(), UserHandle.of(userId)),
- PermissionThread.getHandler());
- permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
- PermissionThread.getExecutor(), successful -> {
- if (successful) {
- future.complete(null);
- } else {
- // We are in an undefined state now, let us crash and have
- // rescue party suggest a wipe to recover to a good one.
- final String message = "Error granting/upgrading runtime permissions"
- + " for user " + userId;
- Slog.wtf(LOG_TAG, message);
- future.completeExceptionally(new IllegalStateException(message));
- }
- });
- try {
- t.traceBegin("Permission_callback_waiting-" + userId);
- future.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new IllegalStateException(e);
- } finally {
- t.traceEnd();
- }
-
- permissionControllerManager.updateUserSensitive();
-
- packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
+ // We need to create a local manager that does not schedule work on the main
+ // there as we are on the main thread and want to block until the work is
+ // completed or we time out.
+ final PermissionControllerManager permissionControllerManager =
+ new PermissionControllerManager(
+ getUserContext(getContext(), UserHandle.of(userId)),
+ PermissionThread.getHandler());
+ permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
+ PermissionThread.getExecutor(), successful -> {
+ if (successful) {
+ future.complete(null);
+ } else {
+ // We are in an undefined state now, let us crash and have
+ // rescue party suggest a wipe to recover to a good one.
+ final String message = "Error granting/upgrading runtime permissions"
+ + " for user " + userId;
+ Slog.wtf(LOG_TAG, message);
+ future.completeExceptionally(new IllegalStateException(message));
+ }
+ });
+ try {
+ t.traceBegin("Permission_callback_waiting-" + userId);
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ t.traceEnd();
}
}
+ private void updateUserSensitive(@UserIdInt int userId) {
+ if (DEBUG) Slog.i(LOG_TAG, "updateUserSensitive(" + userId + ")");
+ final PermissionControllerManager permissionControllerManager =
+ new PermissionControllerManager(
+ getUserContext(getContext(), UserHandle.of(userId)),
+ PermissionThread.getHandler());
+ permissionControllerManager.updateUserSensitive();
+ }
+
private static @Nullable Context getUserContext(@NonNull Context context,
@Nullable UserHandle user) {
if (context.getUser().equals(user)) {
@@ -695,12 +697,10 @@
if (DEBUG) Slog.i(LOG_TAG, "synchronizePermissionsAndAppOpsForUser(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- final PackageManagerInternal packageManagerInternal = LocalServices.getService(
- PackageManagerInternal.class);
final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
getUserContext(getContext(), UserHandle.of(userId)));
t.traceBegin("Permission_synchronize_addPackages-" + userId);
- packageManagerInternal.forEachPackage(
+ mPackageManagerInternal.forEachPackage(
(pkg) -> synchronizer.addPackage(pkg.getPackageName()));
t.traceEnd();
t.traceBegin("Permission_syncPackages-" + userId);
@@ -1052,13 +1052,11 @@
* @param pkgName The package to add for later processing.
*/
void addPackage(@NonNull String pkgName) {
- PackageManagerInternal pmInternal =
- LocalServices.getService(PackageManagerInternal.class);
final PackageInfo pkgInfo;
final AndroidPackage pkg;
try {
pkgInfo = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
- pkg = pmInternal.getPackage(pkgName);
+ pkg = mPackageManagerInternal.getPackage(pkgName);
} catch (NameNotFoundException e) {
return;
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 2d76c50..4ad4353 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -38,6 +38,7 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +67,7 @@
private final int mDismissDialogTimeout;
@Nullable
private SideFpsToast mDialog;
+ private final AccessibilityManager mAccessibilityManager;
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
@@ -96,6 +98,7 @@
DialogProvider provider) {
mContext = context;
mHandler = handler;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mPowerManager = powerManager;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
@@ -157,7 +160,9 @@
mHandler.removeCallbacks(mTurnOffDialog);
}
showDialog(eventTime, "Enroll Power Press");
- mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ if (!mAccessibilityManager.isEnabled()) {
+ mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
+ }
});
return true;
case STATE_BP_AUTH:
@@ -231,6 +236,10 @@
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
+ if (mAccessibilityManager != null
+ && mAccessibilityManager.isEnabled()) {
+ dismissDialog("mTurnOffDialog");
+ }
}
});
mSideFpsEventHandlerReady.set(true);
@@ -256,6 +265,9 @@
mLastPowerPressTime = time;
mDialog.show();
mDialog.setOnClickListener(this);
+ if (mAccessibilityManager.isEnabled()) {
+ mDialog.addAccessibilityDelegate();
+ }
}
interface DialogProvider {
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
index db07467..c27753c 100644
--- a/services/core/java/com/android/server/policy/SideFpsToast.java
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -23,6 +24,7 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import com.android.internal.R;
@@ -34,7 +36,6 @@
* This dialog is used by {@link SideFpsEventHandler}
*/
public class SideFpsToast extends Dialog {
-
SideFpsToast(Context context) {
super(context);
}
@@ -66,4 +67,27 @@
turnOffScreen.setOnClickListener(listener);
}
}
+
+ /**
+ * When accessibility mode is on, add AccessibilityDelegate to dismiss dialog when focus is
+ * moved away from the dialog.
+ */
+ public void addAccessibilityDelegate() {
+ final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+ if (turnOffScreen != null) {
+ turnOffScreen.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull View host,
+ @NonNull AccessibilityEvent event) {
+ if (event.getEventType()
+ == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ && isShowing()) {
+ dismiss();
+ }
+ super.onInitializeAccessibilityEvent(host, event);
+ }
+ });
+
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bbb59ce..76cedd8 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -444,6 +444,9 @@
// Refer to autosuspend.h.
private boolean mHalAutoSuspendModeEnabled;
+ // True if the device uses auto-suspend mode.
+ private final boolean mUseAutoSuspend;
+
// True if interactive mode is enabled.
// Refer to power.h.
private boolean mHalInteractiveModeEnabled;
@@ -1203,6 +1206,9 @@
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
+ mUseAutoSuspend = mContext.getResources().getBoolean(com.android.internal.R.bool
+ .config_useAutoSuspend);
+
// Save brightness values:
// Get float values from config.
// Store float if valid
@@ -3918,6 +3924,9 @@
@GuardedBy("mLock")
private void setHalAutoSuspendModeLocked(boolean enable) {
+ if (!mUseAutoSuspend) {
+ return;
+ }
if (enable != mHalAutoSuspendModeEnabled) {
if (DEBUG) {
Slog.d(TAG, "Setting HAL auto-suspend mode to " + enable);
@@ -4661,6 +4670,7 @@
pw.println(" mEnhancedDischargePredictionIsPersonalized="
+ mEnhancedDischargePredictionIsPersonalized);
}
+ pw.println(" mUseAutoSuspend=" + mUseAutoSuspend);
pw.println(" mHalAutoSuspendModeEnabled=" + mHalAutoSuspendModeEnabled);
pw.println(" mHalInteractiveModeEnabled=" + mHalInteractiveModeEnabled);
pw.println(" mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 592d039..12db21d 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -40,6 +40,7 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -211,10 +212,6 @@
@GuardedBy("mLock")
private int mTotalStarted = 0;
- /** The total number of timers that were restarted without an explicit cancel. */
- @GuardedBy("mLock")
- private int mTotalRestarted = 0;
-
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -368,7 +365,7 @@
abstract boolean enabled();
- abstract void dump(PrintWriter pw, boolean verbose);
+ abstract void dump(IndentingPrintWriter pw, boolean verbose);
abstract void close();
}
@@ -410,9 +407,14 @@
return false;
}
- /** dump() is a no-op when the feature is disabled. */
+ /** Dump the limited statistics captured when the feature is disabled. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
+ synchronized (mLock) {
+ pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ mTotalExpired, mTotalErrors);
+ }
}
/** close() is a no-op when the feature is disabled. */
@@ -441,6 +443,10 @@
*/
private long mNative = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** Fetch the native tag (an integer) for the given label. */
FeatureEnabled() {
mNative = nativeAnrTimerCreate(mLabel);
@@ -537,13 +543,22 @@
/** Dump statistics from the native layer. */
@Override
- void dump(PrintWriter pw, boolean verbose) {
+ void dump(IndentingPrintWriter pw, boolean verbose) {
synchronized (mLock) {
- if (mNative != 0) {
- nativeAnrTimerDump(mNative, verbose);
- } else {
+ if (mNative == 0) {
pw.println("closed");
+ return;
}
+ String[] nativeDump = nativeAnrTimerDump(mNative);
+ if (nativeDump == null) {
+ pw.println("no-data");
+ return;
+ }
+ for (String s : nativeDump) {
+ pw.println(s);
+ }
+ // The following counter is only available at the Java level.
+ pw.println("restarted:" + mTotalRestarted);
}
}
@@ -690,11 +705,8 @@
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
- mTotalExpired, mTotalErrors);
- pw.decreaseIndent();
mFeature.dump(pw, false);
+ pw.decreaseIndent();
}
}
@@ -755,6 +767,14 @@
recordErrorLocked(operation, "notFound", arg);
}
+ /** Compare two AnrTimers in display order. */
+ private static final Comparator<AnrTimer> sComparator =
+ Comparator.nullsLast(new Comparator<>() {
+ @Override
+ public int compare(AnrTimer o1, AnrTimer o2) {
+ return o1.mLabel.compareTo(o2.mLabel);
+ }});
+
/** Dumpsys output, allowing for overrides. */
@VisibleForTesting
static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
@@ -764,11 +784,18 @@
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
synchronized (sAnrTimerList) {
+ // Find the currently live instances and sort them by their label. The goal is to
+ // have consistent output ordering.
final int size = sAnrTimerList.size();
- ipw.println("reporting " + size + " timers");
+ AnrTimer[] active = new AnrTimer[size];
+ int valid = 0;
for (int i = 0; i < size; i++) {
AnrTimer a = sAnrTimerList.valueAt(i).get();
- if (a != null) a.dump(ipw);
+ if (a != null) active[valid++] = a;
+ }
+ Arrays.sort(active, 0, valid, sComparator);
+ for (int i = 0; i < valid; i++) {
+ if (active[i] != null) active[i].dump(ipw);
}
}
if (verbose) dumpErrors(ipw);
@@ -827,6 +854,6 @@
/** Discard an expired timer by ID. Return true if the timer was found. */
private static native boolean nativeAnrTimerDiscard(long service, int timerId);
- /** Prod the native library to log a few statistics. */
- private static native void nativeAnrTimerDump(long service, boolean verbose);
+ /** Retrieve runtime dump information from the native layer. */
+ private static native String[] nativeAnrTimerDump(long service);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1f320da..7d057a9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9817,10 +9817,10 @@
if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
return mLetterboxUiController.getUserMinAspectRatio();
}
- if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
+ if (!mLetterboxUiController.shouldOverrideMinAspectRatio()
+ && !mLetterboxUiController.shouldOverrideMinAspectRatioForCamera()) {
return info.getMinAspectRatio();
}
-
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
&& !ActivityInfo.isFixedOrientationPortrait(
getOverrideOrientation())) {
@@ -9941,6 +9941,16 @@
return updateReportedConfigurationAndSend();
}
+ /**
+ * @return {@code true} if the Camera is active for the current activity
+ */
+ boolean isCameraActive() {
+ return mDisplayContent != null
+ && mDisplayContent.getDisplayRotationCompatPolicy() != null
+ && mDisplayContent.getDisplayRotationCompatPolicy()
+ .isCameraActive(this, /* mustBeFullscreen */ true);
+ }
+
boolean updateReportedConfigurationAndSend() {
if (isConfigurationDispatchPaused()) {
Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f7e5dd8..2f37e88 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1318,6 +1318,15 @@
}
}
+ /**
+ * @return The {@link DisplayRotationCompatPolicy} for this DisplayContent
+ */
+ // TODO(b/335387481) Allow access to DisplayRotationCompatPolicy only with getters
+ @Nullable
+ DisplayRotationCompatPolicy getDisplayRotationCompatPolicy() {
+ return mDisplayRotationCompatPolicy;
+ }
+
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
@@ -2021,12 +2030,11 @@
}
// Update directly because the app which will change the orientation of display is ready.
if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
- // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread().
- mWmService.mH.post(() -> {
- synchronized (mWmService.mGlobalLock) {
- sendNewConfiguration();
- }
- });
+ // If a transition is collecting, let the transition apply the rotation change on
+ // display thread. See Transition#shouldApplyOnDisplayThread().
+ if (!mTransitionController.isCollecting(this)) {
+ sendNewConfiguration();
+ }
return;
}
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index d5f8df3..eacf9a3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -64,7 +64,7 @@
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
*/
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
// Delay for updating display rotation after Camera connection is closed. Needed to avoid
// rotation flickering when an app is flipping between front and rear cameras or when size
@@ -306,7 +306,8 @@
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
- && isCameraActive(activity, /* mustBeFullscreen */ true);
+ && isCameraActive(activity, /* mustBeFullscreen */ true)
+ && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -324,6 +325,13 @@
return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
}
+ boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ // Checking windowing mode on activity level because we don't want to
+ // apply treatment in case of activity embedding.
+ return (!mustBeFullscreen || !activity.inMultiWindowMode())
+ && mCameraStateMonitor.isCameraRunningForActivity(activity);
+ }
+
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
boolean mustBeFullscreen) {
return activity != null && isCameraActive(activity, mustBeFullscreen)
@@ -331,14 +339,7 @@
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
- && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
- }
-
- private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
- // Checking windowing mode on activity level because we don't want to
- // apply treatment in case of activity embedding.
- return (!mustBeFullscreen || !activity.inMultiWindowMode())
- && mCameraStateMonitor.isCameraRunningForActivity(activity)
+ && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 683efde..b38e666 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -510,6 +511,26 @@
}
/**
+ * Whether we should apply the min aspect ratio per-app override only when an app is connected
+ * to the camera.
+ * When this override is applied the min aspect ratio given in the app's manifest will be
+ * overridden to the largest enabled aspect ratio treatment unless the app's manifest value
+ * is higher. The treatment will also apply if no value is provided in the manifest.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideMinAspectRatioForCamera() {
+ return mActivityRecord.isCameraActive()
+ && mAllowMinAspectRatioOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ }
+
+ /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
@@ -962,7 +983,8 @@
void recomputeConfigurationForCameraCompatIfNeeded() {
if (isOverrideOrientationOnlyForCameraEnabled()
- || isCameraCompatSplitScreenAspectRatioAllowed()) {
+ || isCameraCompatSplitScreenAspectRatioAllowed()
+ || shouldOverrideMinAspectRatioForCamera()) {
mActivityRecord.recomputeConfiguration();
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9b98380..301d0a5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -43,7 +43,6 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -641,8 +640,6 @@
mLastTaskSnapshotData = _lastSnapshotData != null
? _lastSnapshotData
: new PersistedTaskSnapshotData();
- // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
- setOrientation(SCREEN_ORIENTATION_UNSET);
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a437914..cb8304c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -186,7 +186,7 @@
// The specified orientation for this window container.
// Shouldn't be accessed directly since subclasses can override getOverrideOrientation.
@ScreenOrientation
- private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ private int mOverrideOrientation = SCREEN_ORIENTATION_UNSET;
/**
* The window container which decides its orientation since the last time
@@ -1683,8 +1683,6 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- // TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs.
- // SCREEN_ORIENTATION_UNSPECIFIED?
final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
if (orientation == SCREEN_ORIENTATION_BEHIND) {
@@ -1700,7 +1698,7 @@
continue;
}
- if (wc.providesOrientation() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ if (orientation != SCREEN_ORIENTATION_UNSPECIFIED || wc.providesOrientation()) {
// Use the orientation if the container can provide or requested an explicit
// orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
ProtoLog.v(WM_DEBUG_ORIENTATION, "%s is requesting orientation %d (%s)",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 25c5db1..64cbb0d1 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -12,6 +12,7 @@
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_adb_* = file:/services/core/java/com/android/server/adb/OWNERS
per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
diff --git a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
index 9c834aa..c7b6852 100644
--- a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
+++ b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
@@ -18,58 +18,22 @@
#define LOG_NDEBUG 0
-#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <optional>
-#include <random>
-#include <string>
-#include <vector>
#include <adb/pairing/pairing_server.h>
#include <android-base/properties.h>
-#include <utils/Log.h>
-
+#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include "jni.h"
+#include <nativehelper/utils.h>
+#include <utils/Log.h>
namespace android {
// ----------------------------------------------------------------------------
namespace {
-template <class T, class N>
-class JSmartWrapper {
-public:
- JSmartWrapper(JNIEnv* env, T* jData) : mEnv(env), mJData(jData) {}
-
- virtual ~JSmartWrapper() = default;
-
- const N* data() const { return mRawData; }
-
- jsize size() const { return mSize; }
-
-protected:
- N* mRawData = nullptr;
- JNIEnv* mEnv = nullptr;
- T* mJData = nullptr;
- jsize mSize = 0;
-}; // JSmartWrapper
-
-class JStringUTFWrapper : public JSmartWrapper<jstring, const char> {
-public:
- explicit JStringUTFWrapper(JNIEnv* env, jstring* str) : JSmartWrapper(env, str) {
- mRawData = env->GetStringUTFChars(*str, NULL);
- mSize = env->GetStringUTFLength(*str);
- }
-
- virtual ~JStringUTFWrapper() {
- if (data()) {
- mEnv->ReleaseStringUTFChars(*mJData, mRawData);
- }
- }
-}; // JStringUTFWrapper
-
struct ServerDeleter {
void operator()(PairingServerCtx* p) { pairing_server_destroy(p); }
};
@@ -97,19 +61,19 @@
std::unique_ptr<PairingResultWaiter> sWaiter;
} // namespace
-static jint native_pairing_start(JNIEnv* env, jobject thiz, jstring guid, jstring password) {
+static jint native_pairing_start(JNIEnv* env, jobject thiz, jstring javaGuid, jstring javaPassword) {
// Server-side only sends its GUID on success.
- PeerInfo system_info = {};
- system_info.type = ADB_DEVICE_GUID;
- JStringUTFWrapper guidWrapper(env, &guid);
- memcpy(system_info.data, guidWrapper.data(), guidWrapper.size());
+ PeerInfo system_info = { .type = ADB_DEVICE_GUID };
- JStringUTFWrapper passwordWrapper(env, &password);
+ ScopedUtfChars guid = GET_UTF_OR_RETURN(env, javaGuid);
+ memcpy(system_info.data, guid.c_str(), guid.size());
+
+ ScopedUtfChars password = GET_UTF_OR_RETURN(env, javaPassword);
// Create the pairing server
sServer = PairingServerPtr(
- pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(passwordWrapper.data()),
- passwordWrapper.size(), &system_info, 0));
+ pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(password.c_str()),
+ password.size(), &system_info, 0));
sWaiter.reset(new PairingResultWaiter);
uint16_t port = pairing_server_start(sServer.get(), sWaiter->ResultCallback, sWaiter.get());
@@ -137,11 +101,16 @@
return JNI_FALSE;
}
- std::string peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
- // Write to PairingThread's member variables
+ // Create a Java string for the public key.
+ char* peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
+ jstring jpublickey = env->NewStringUTF(peer_public_key);
+ if (jpublickey == nullptr) {
+ return JNI_FALSE;
+ }
+
+ // Write to PairingThread.mPublicKey.
jclass clazz = env->GetObjectClass(thiz);
jfieldID mPublicKey = env->GetFieldID(clazz, "mPublicKey", "Ljava/lang/String;");
- jstring jpublickey = env->NewStringUTF(peer_public_key.c_str());
env->SetObjectField(thiz, mPublicKey, jpublickey);
return JNI_TRUE;
}
@@ -157,12 +126,9 @@
};
int register_android_server_AdbDebuggingManager(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env,
- "com/android/server/adb/AdbDebuggingManager$PairingThread",
- gPairingThreadMethods, NELEM(gPairingThreadMethods));
- (void)res; // Faked use when LOG_NDEBUG.
- LOG_FATAL_IF(res < 0, "Unable to register native methods.");
- return 0;
+ return jniRegisterNativeMethods(env,
+ "com/android/server/adb/AdbDebuggingManager$PairingThread",
+ gPairingThreadMethods, NELEM(gPairingThreadMethods));
}
} /* namespace android */
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index da95666..6509958 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -161,14 +161,14 @@
// A timer has expired.
void expire(timer_id_t);
- // Dump a small amount of state to the log file.
- void dump(bool verbose) const;
-
// Return the Java object associated with this instance.
jweak jtimer() const {
return notifierObject_;
}
+ // Return the per-instance statistics.
+ std::vector<std::string> getDump() const;
+
private:
// The service cannot be copied.
AnrTimerService(AnrTimerService const &) = delete;
@@ -199,7 +199,7 @@
std::set<Timer> running_;
// The maximum number of active timers.
- size_t maxActive_;
+ size_t maxRunning_;
// Simple counters
struct Counters {
@@ -209,6 +209,7 @@
size_t accepted;
size_t discarded;
size_t expired;
+ size_t extended;
// The number of times there were zero active timers.
size_t drained;
@@ -437,7 +438,9 @@
// Construct the ticker. This creates the timerfd file descriptor and starts the monitor
// thread. The monitor thread is given a unique name.
- Ticker() {
+ Ticker() :
+ id_(idGen_.fetch_add(1))
+ {
timerFd_ = timer_create();
if (timerFd_ < 0) {
ALOGE("failed to create timerFd: %s", strerror(errno));
@@ -502,6 +505,11 @@
}
}
+ // The unique ID of this particular ticker. Used for debug and logging.
+ size_t id() const {
+ return id_;
+ }
+
// Return the number of timers still running.
size_t running() const {
AutoMutex _l(lock_);
@@ -617,8 +625,16 @@
// The list of timers that are scheduled. This set is sorted by timeout and then by timer
// ID. A set is sufficient (as opposed to a multiset) because timer IDs are unique.
std::set<Entry> running_;
+
+ // A unique ID assigned to this instance.
+ const size_t id_;
+
+ // The ID generator.
+ static std::atomic<size_t> idGen_;
};
+std::atomic<size_t> AnrTimerService::Ticker::idGen_;
+
AnrTimerService::AnrTimerService(char const* label,
notifier_t notifier, void* cookie, jweak jtimer, Ticker* ticker) :
@@ -629,7 +645,7 @@
ticker_(ticker) {
// Zero the statistics
- maxActive_ = 0;
+ maxRunning_ = 0;
memset(&counters_, 0, sizeof(counters_));
ALOGI_IF(DEBUG, "initialized %s", label);
@@ -739,6 +755,12 @@
elapsed = now() - t.started;
}
+ if (expired) {
+ counters_.expired++;
+ } else {
+ counters_.extended++;
+ }
+
// Deliver the notification outside of the lock.
if (expired) {
if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
@@ -756,8 +778,8 @@
if (t.status == Running) {
// Only forward running timers to the ticker. Expired timers are handled separately.
ticker_->insert(t.scheduled, t.id, this);
- maxActive_ = std::max(maxActive_, running_.size());
}
+ maxRunning_ = std::max(maxRunning_, running_.size());
}
AnrTimerService::Timer AnrTimerService::remove(timer_id_t timerId) {
@@ -767,29 +789,32 @@
Timer result = *found;
running_.erase(found);
ticker_->remove(result.scheduled, result.id);
+ if (running_.size() == 0) counters_.drained++;
return result;
}
return Timer();
}
-void AnrTimerService::dump(bool verbose) const {
+std::vector<std::string> AnrTimerService::getDump() const {
+ std::vector<std::string> r;
AutoMutex _l(lock_);
- ALOGI("timer %s ops started=%zu canceled=%zu accepted=%zu discarded=%zu expired=%zu",
- label_.c_str(),
- counters_.started, counters_.canceled, counters_.accepted,
- counters_.discarded, counters_.expired);
- ALOGI("timer %s stats max-active=%zu/%zu running=%zu/%zu errors=%zu",
- label_.c_str(),
- maxActive_, ticker_->maxRunning(), running_.size(), ticker_->running(),
- counters_.error);
-
- if (verbose) {
- nsecs_t time = now();
- for (auto i = running_.begin(); i != running_.end(); i++) {
- Timer t = *i;
- ALOGI(" running %s", t.toString(time).c_str());
- }
- }
+ r.push_back(StringPrintf("started:%zu canceled:%zu accepted:%zu discarded:%zu expired:%zu",
+ counters_.started,
+ counters_.canceled,
+ counters_.accepted,
+ counters_.discarded,
+ counters_.expired));
+ r.push_back(StringPrintf("extended:%zu drained:%zu error:%zu running:%zu maxRunning:%zu",
+ counters_.extended,
+ counters_.drained,
+ counters_.error,
+ running_.size(),
+ maxRunning_));
+ r.push_back(StringPrintf("ticker:%zu ticking:%zu maxTicking:%zu",
+ ticker_->id(),
+ ticker_->running(),
+ ticker_->maxRunning()));
+ return r;
}
/**
@@ -894,21 +919,26 @@
return toService(ptr)->discard(timerId);
}
-jint anrTimerDump(JNIEnv *env, jclass, jlong ptr, jboolean verbose) {
- if (!nativeSupportEnabled) return -1;
- toService(ptr)->dump(verbose);
- return 0;
+jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {
+ if (!nativeSupportEnabled) return nullptr;
+ std::vector<std::string> stats = toService(ptr)->getDump();
+ jclass sclass = env->FindClass("java/lang/String");
+ jobjectArray r = env->NewObjectArray(stats.size(), sclass, nullptr);
+ for (size_t i = 0; i < stats.size(); i++) {
+ env->SetObjectArrayElement(r, i, env->NewStringUTF(stats[i].c_str()));
+ }
+ return r;
}
static const JNINativeMethod methods[] = {
{"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
- {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
- {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
- {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
- {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
- {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
- {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
- {"nativeAnrTimerDump", "(JZ)V", (void*) anrTimerDump},
+ {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
+ {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
+ {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
+ {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
+ {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
+ {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
+ {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
};
} // anonymous namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d555f1a..85ab562 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21610,7 +21610,8 @@
== HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
}
- if (Flags.headlessSingleUserFixes() && isSingleUserMode && !mInjector.isChangeEnabled(
+ if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
+ && isSingleUserMode && !mInjector.isChangeEnabled(
PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
throw new IllegalStateException("Device admin is not targeting Android V.");
}
@@ -21629,7 +21630,7 @@
setLocale(provisioningParams.getLocale());
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && isSingleUserMode
+ && isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
new file mode 100644
index 0000000..f0abcd2
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import android.content.Context
+import android.util.SparseArray
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
+import com.android.server.display.mode.RefreshRateVote.RenderVote
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class AppRequestObserverTest {
+
+ private lateinit var context: Context
+ private val mockInjector = mock<DisplayModeDirector.Injector>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockDisplayDeviceConfigProvider = mock<DisplayDeviceConfigProvider>()
+ private val testHandler = TestHandler(null)
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun `test app request votes`(@TestParameter testCase: AppRequestTestCase) {
+ whenever(mockFlags.ignoreAppPreferredRefreshRateRequest())
+ .thenReturn(testCase.ignoreRefreshRateRequest)
+ val displayModeDirector = DisplayModeDirector(
+ context, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val modes = arrayOf(
+ Display.Mode(1, 1000, 1000, 60f),
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
+ )
+ displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
+ append(Display.DEFAULT_DISPLAY, modes)
+ })
+ displayModeDirector.injectDefaultModeByDisplay(SparseArray<Display.Mode>().apply {
+ append(Display.DEFAULT_DISPLAY, modes[0])
+ })
+
+ displayModeDirector.appRequestObserver.setAppRequest(Display.DEFAULT_DISPLAY,
+ testCase.modeId,
+ testCase.requestedRefreshRates,
+ testCase.requestedMinRefreshRates,
+ testCase.requestedMaxRefreshRates)
+
+ val baseModeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE)
+ assertThat(baseModeVote).isEqualTo(testCase.expectedBaseModeVote)
+
+ val sizeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_SIZE)
+ assertThat(sizeVote).isEqualTo(testCase.expectedSizeVote)
+
+ val renderRateVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE)
+ assertThat(renderRateVote).isEqualTo(testCase.expectedRenderRateVote)
+ }
+
+ enum class AppRequestTestCase(
+ val ignoreRefreshRateRequest: Boolean,
+ val modeId: Int,
+ val requestedRefreshRates: Float,
+ val requestedMinRefreshRates: Float,
+ val requestedMaxRefreshRates: Float,
+ internal val expectedBaseModeVote: Vote?,
+ internal val expectedSizeVote: Vote?,
+ internal val expectedRenderRateVote: Vote?,
+ ) {
+ BASE_MODE_60(true, 1, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ BASE_MODE_90(true, 2, 0f, 0f, 0f,
+ BaseModeRefreshRateVote(90f), SizeVote(1000, 1000, 1000, 1000), null),
+ MIN_REFRESH_RATE_60(true, 0, 0f, 60f, 0f,
+ null, null, RenderVote(60f, Float.POSITIVE_INFINITY)),
+ MIN_REFRESH_RATE_120(true, 0, 0f, 120f, 0f,
+ null, null, RenderVote(120f, Float.POSITIVE_INFINITY)),
+ MAX_REFRESH_RATE_60(true, 0, 0f, 0f, 60f,
+ null, null, RenderVote(0f, 60f)),
+ MAX_REFRESH_RATE_120(true, 0, 0f, 0f, 120f,
+ null, null, RenderVote(0f, 120f)),
+ INVALID_MIN_MAX_REFRESH_RATE(true, 0, 0f, 90f, 60f,
+ null, null, null),
+ BASE_MODE_MIN_MAX(true, 1, 0f, 60f, 90f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
+ PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
+ BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
+ PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
+ null, null, null),
+ PREFERRED_REFRESH_RATE_INVALID(false, 0, 45f, 0f, 0f,
+ null, null, null),
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 52e157b..cd1e9e8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -31,7 +31,6 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -2262,162 +2261,6 @@
}
@Test
- public void testAppRequestObserver_modeId() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(vote);
- }
-
- @Test
- public void testAppRequestObserver_minRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isAtLeast(90);
- }
-
- @Test
- public void testAppRequestObserver_maxRefreshRate() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
-
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
- appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isZero();
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
- }
-
- @Test
- public void testAppRequestObserver_invalidRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 60);
- Vote appRequestRefreshRate =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNull(appRequestRefreshRate);
-
- Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNull(appRequestSize);
-
- Vote appRequestRefreshRateRange =
- director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNull(appRequestRefreshRateRange);
- }
-
- @Test
- public void testAppRequestObserver_modeIdAndRefreshRateRange() {
- DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
- director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
-
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class);
- BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote;
- assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(SizeVote.class);
- SizeVote sizeVote = (SizeVote) vote;
- assertThat(sizeVote.mHeight).isEqualTo(1000);
- assertThat(sizeVote.mWidth).isEqualTo(1000);
- assertThat(sizeVote.mMinHeight).isEqualTo(1000);
- assertThat(sizeVote.mMinWidth).isEqualTo(1000);
-
- vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
- assertNotNull(vote);
- assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class);
- RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote;
- assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
- }
-
- @Test
public void testAppRequestsIsTheDefaultMode() {
Display.Mode[] modes = new Display.Mode[2];
modes[0] = new Display.Mode(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 3e0677c..4d910ce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -146,8 +146,8 @@
val modes = arrayOf(
Display.Mode(1, 1000, 1000, 60f),
- Display.Mode(1, 1000, 1000, 90f),
- Display.Mode(1, 1000, 1000, 120f)
+ Display.Mode(2, 1000, 1000, 90f),
+ Display.Mode(3, 1000, 1000, 120f)
)
displayModeDirector.injectSupportedModesByDisplay(SparseArray<Array<Display.Mode>>().apply {
append(Display.DEFAULT_DISPLAY, modes)
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index a8b792e..80f7a06 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -197,7 +197,7 @@
+ Arrays.toString(invocation.getArguments()));
if (!wedge) {
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
}
return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 67be93b..89c67d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -200,7 +200,7 @@
Log.v(TAG, "Intercepting bindApplication() for "
+ Arrays.toString(invocation.getArguments()));
if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
- mRealAms.finishAttachApplication(0);
+ mRealAms.finishAttachApplication(0, 0);
}
return null;
}).when(thread).bindApplication(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index e672928..599b9cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -24,6 +24,7 @@
import android.content.ContentResolver;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -42,6 +43,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Test SettingsToPropertiesMapper.
@@ -61,6 +63,7 @@
private HashMap<String, String> mSystemSettingsMap;
private HashMap<String, String> mGlobalSettingsMap;
+ private HashMap<String, String> mConfigSettingsMap;
@Before
public void setUp() throws Exception {
@@ -71,9 +74,11 @@
.spyStatic(SystemProperties.class)
.spyStatic(Settings.Global.class)
.spyStatic(SettingsToPropertiesMapper.class)
+ .spyStatic(Settings.Config.class)
.startMocking();
mSystemSettingsMap = new HashMap<>();
mGlobalSettingsMap = new HashMap<>();
+ mConfigSettingsMap = new HashMap<>();
// Mock SystemProperties setter and various getters
doAnswer((Answer<Void>) invocationOnMock -> {
@@ -93,7 +98,7 @@
}
).when(() -> SystemProperties.get(anyString()));
- // Mock Settings.Global methods
+ // Mock Settings.Global method
doAnswer((Answer<String>) invocationOnMock -> {
String key = invocationOnMock.getArgument(1);
@@ -101,6 +106,21 @@
}
).when(() -> Settings.Global.getString(any(), anyString()));
+ // Mock Settings.Config getstrings method
+ doAnswer((Answer<Map<String, String>>) invocationOnMock -> {
+ String namespace = invocationOnMock.getArgument(0);
+ List<String> flags = invocationOnMock.getArgument(1);
+ HashMap<String, String> values = new HashMap<>();
+ for (String flag : flags) {
+ String value = mConfigSettingsMap.get(namespace + "/" + flag);
+ if (value != null) {
+ values.put(flag, value);
+ }
+ }
+ return values;
+ }
+ ).when(() -> Settings.Config.getStrings(anyString(), any()));
+
mTestMapper = new SettingsToPropertiesMapper(
mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
}
@@ -239,4 +259,43 @@
Assert.assertTrue(categories.contains("category2"));
Assert.assertTrue(categories.contains("category3"));
}
+
+ @Test
+ public void testGetStagedFlagsWithValueChange() {
+ // mock up what is in the setting already
+ mConfigSettingsMap.put("namespace_1/flag_1", "true");
+ mConfigSettingsMap.put("namespace_1/flag_2", "true");
+
+ // mock up input
+ String namespace = "staged";
+ Map<String, String> keyValueMap = new HashMap<>();
+ // case 1: existing prop, stage the same value
+ keyValueMap.put("namespace_1*flag_1", "true");
+ // case 2: existing prop, stage a different value
+ keyValueMap.put("namespace_1*flag_2", "false");
+ // case 3: new prop, stage the non default value
+ keyValueMap.put("namespace_2*flag_1", "true");
+ // case 4: new prop, stage the default value
+ keyValueMap.put("namespace_2*flag_2", "false");
+ Properties props = new Properties(namespace, keyValueMap);
+
+ HashMap<String, HashMap<String, String>> toStageProps =
+ SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props);
+
+ HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1");
+ HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2");
+ Assert.assertTrue(namespace_1_to_stage != null);
+ Assert.assertTrue(namespace_2_to_stage != null);
+
+ String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
+ String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
+ String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
+ String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2");
+ Assert.assertTrue(namespace_1_flag_1 == null);
+ Assert.assertTrue(namespace_1_flag_2 != null);
+ Assert.assertTrue(namespace_2_flag_1 != null);
+ Assert.assertTrue(namespace_2_flag_2 == null);
+ Assert.assertTrue(namespace_1_flag_2.equals("false"));
+ Assert.assertTrue(namespace_2_flag_1.equals("true"));
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 5c74a80..7f165e0 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -3241,6 +3241,48 @@
}
}
+ @Test
+ public void testHalAutoSuspendMode_enabledByConfiguration() {
+ AtomicReference<DisplayManagerInternal.DisplayPowerCallbacks> callback =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ callback.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).initPowerManagement(any(), any(), any());
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_powerDecoupleAutoSuspendModeFromDisplay))
+ .thenReturn(false);
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_useAutoSuspend))
+ .thenReturn(true);
+
+ createService();
+ startSystem();
+ callback.get().onDisplayStateChange(/* allInactive= */ true, /* allOff= */ true);
+
+ verify(mNativeWrapperMock).nativeSetAutoSuspend(true);
+ }
+
+ @Test
+ public void testHalAutoSuspendMode_disabledByConfiguration() {
+ AtomicReference<DisplayManagerInternal.DisplayPowerCallbacks> callback =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ callback.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).initPowerManagement(any(), any(), any());
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_powerDecoupleAutoSuspendModeFromDisplay))
+ .thenReturn(false);
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_useAutoSuspend))
+ .thenReturn(false);
+
+ createService();
+ startSystem();
+ callback.get().onDisplayStateChange(/* allInactive= */ true, /* allOff= */ true);
+
+ verify(mNativeWrapperMock, never()).nativeSetAutoSuspend(true);
+ }
+
private void setCachedUidProcState(int uid) {
mService.updateUidProcStateInternal(uid, PROCESS_STATE_TOP_SLEEPING);
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 94a71be..753db12 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -279,7 +279,7 @@
test_module_config {
name: "FrameworksServicesTests_contentprotection",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.contentprotection"],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -287,7 +287,7 @@
test_module_config {
name: "FrameworksServicesTests_om",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.om."],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -296,7 +296,7 @@
test_module_config {
name: "FrameworksServicesTests_contexthub_presubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.location.contexthub."],
// TODO(ron): are these right, does it run anything?
include_annotations: ["android.platform.test.annotations.Presubmit"],
@@ -306,7 +306,7 @@
test_module_config {
name: "FrameworksServicesTests_contexthub_postsubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.location.contexthub."],
// TODO(ron): are these right, does it run anything?
include_annotations: ["android.platform.test.annotations.Postsubmit"],
@@ -317,7 +317,7 @@
test_module_config {
name: "FrameworksServicesTests_contentcapture",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.contentcapture"],
exclude_annotations: FLAKY_AND_IGNORED,
}
@@ -325,7 +325,7 @@
test_module_config {
name: "FrameworksServicesTests_recoverysystem",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.recoverysystem."],
exclude_annotations: ["androidx.test.filters.FlakyTest"],
}
@@ -334,7 +334,7 @@
test_module_config {
name: "FrameworksServicesTests_pm_presubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_annotations: ["android.platform.test.annotations.Presubmit"],
include_filters: ["com.android.server.pm."],
exclude_annotations: FLAKY_AND_IGNORED,
@@ -343,7 +343,7 @@
test_module_config {
name: "FrameworksServicesTests_pm_postsubmit",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_annotations: ["android.platform.test.annotations.Postsubmit"],
include_filters: ["com.android.server.pm."],
exclude_annotations: FLAKY_AND_IGNORED,
@@ -353,6 +353,6 @@
test_module_config {
name: "FrameworksServicesTests_os",
base: "FrameworksServicesTests",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
include_filters: ["com.android.server.os."],
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 669eedf..dabf531 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -31,11 +31,13 @@
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.test.TestLooper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableResources;
import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -48,7 +50,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.List;
@@ -72,9 +75,15 @@
private static final Integer AUTO_DISMISS_DIALOG = 500;
@Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ private final AccessibilityManager mAccessibilityManager =
+ mContext.getSystemService(AccessibilityManager.class);
+
@Mock
private PackageManager mPackageManager;
@Mock
@@ -89,9 +98,8 @@
private BiometricStateListener mBiometricStateListener;
@Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
+ public void setup() throws RemoteException {
+ disableAccessibility();
mContext.addMockSystemService(PackageManager.class, mPackageManager);
mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
TestableResources resources = mContext.getOrCreateTestableResources();
@@ -192,9 +200,8 @@
}
@Test
- public void dialogDismissesAfterTime() throws Exception {
+ public void dialogDismissesAfterTime_accessibilityDisabled() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
-
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -207,9 +214,23 @@
}
@Test
- public void dialogDoesNotDismissOnSensorTouch() throws Exception {
+ public void dialogDoesNotDismissAfterTime_accessibilityEnabled() throws Exception {
+ enableAccessibility();
setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
+ mLooper.dispatchAll();
+ verify(mDialog, never()).dismiss();
+ }
+
+ @Test
+ public void dialogDoesNotDismissOnSensorTouch_accessibilityDisabled() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
@@ -218,12 +239,26 @@
verify(mDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
mLooper.dispatchAll();
-
verify(mDialog, never()).dismiss();
}
+ @Test
+ public void dialogDismissesOnSensorTouch_accessibilityEnabled() throws Exception {
+ enableAccessibility();
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ mLooper.dispatchAll();
+ verify(mDialog).dismiss();
+ }
+
private void setBiometricState(@BiometricStateListener.State int newState) {
if (mBiometricStateListener != null) {
mBiometricStateListener.onStateChanged(newState);
@@ -231,6 +266,20 @@
}
}
+ private void enableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(1);
+ mLooper.dispatchAll();
+ }
+ }
+
+ private void disableAccessibility() throws RemoteException {
+ if (mAccessibilityManager != null) {
+ mAccessibilityManager.getClient().setState(0);
+ mLooper.dispatchAll();
+ }
+ }
+
private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 0d6fdc9..4af20a9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2638,7 +2638,7 @@
@Test
public void testSoundResetsRankingTime() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME);
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME);
TestableFlagResolver flagResolver = new TestableFlagResolver();
initAttentionHelper(flagResolver);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 74d8433..3a0eba1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,7 +24,7 @@
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
-import static android.app.Flags.FLAG_UPDATE_RANKING_TIME;
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -7376,7 +7376,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME})
public void testVisualDifference_userInitiatedJob() {
Notification.Builder nb1 = new Notification.Builder(mContext, "")
.setContentTitle("foo");
@@ -15283,7 +15283,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_noisy_matchesSbn() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, mUserId);
@@ -15297,7 +15297,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_newNotification_silent_matchesSbn() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15312,7 +15312,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentSameText_originalPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
@@ -15332,7 +15332,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_silentNewText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, 0, mUserId);
@@ -15357,7 +15357,7 @@
}
@Test
- @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ @EnableFlags(FLAG_SORT_SECTION_BY_TIME)
public void rankingTime_updatedNotification_noisySameText_newPostTime() throws Exception {
NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
NotificationRecord nr = generateNotificationRecord(low, mUserId);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 9a58594..d1880d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1561,14 +1561,14 @@
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+ loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
- when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_R);
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
- new int[]{UID_P});
+ new int[]{UID_R});
- NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_R, id,
false);
assertThat(channel.getImportance()).isEqualTo(2);
assertThat(channel.canShowBadge()).isTrue();
@@ -1616,7 +1616,7 @@
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
false, mClock);
- loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+ loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 527001d..9a6e818 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -378,7 +378,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldWhenChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -430,7 +430,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_unspecifiedSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -480,7 +480,7 @@
}
@Test
- @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME, Flags.FLAG_UPDATE_RANKING_TIME})
+ @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
public void testSort_oldChildren_oldSummary() {
NotificationRecord child1 = new NotificationRecord(mContext,
new StatusBarNotification(
@@ -517,10 +517,10 @@
mUser, null, System.currentTimeMillis()), getLowChannel());
ArrayList<NotificationRecord> expected = new ArrayList<>();
+ expected.add(unrelated);
expected.add(summary);
expected.add(child2);
expected.add(child1);
- expected.add(unrelated);
ArrayList<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 30eb5ef..1da5001 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -116,6 +116,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
@@ -3507,6 +3508,23 @@
}
@Test
+ public void testIsCameraActive() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final DisplayRotationCompatPolicy displayRotationCompatPolicy = mock(
+ DisplayRotationCompatPolicy.class);
+ when(mDisplayContent.getDisplayRotationCompatPolicy()).thenReturn(
+ displayRotationCompatPolicy);
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(false);
+ assertFalse(app.mActivityRecord.isCameraActive());
+
+ when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
+ anyBoolean())).thenReturn(true);
+ assertTrue(app.mActivityRecord.isCameraActive());
+ }
+
+ @Test
public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
final ActivityRecord activity = createActivityWithTask();
// Mock a flag being enabled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7356b43..27d9d13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1071,6 +1071,7 @@
@Test
public void testAllowsTopmostFullscreenOrientation() {
final DisplayContent dc = createNewDisplay();
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, dc.getOrientation());
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
@@ -1717,7 +1718,6 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
- waitHandlerIdle(mWm.mH);
mStatusBarWindow.finishSeamlessRotation(t);
mNavBarWindow.finishSeamlessRotation(t);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 507140d..262ba8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -38,6 +38,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -538,6 +539,42 @@
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
+ @Test
+ public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.inMultiWindowMode()).thenReturn(true);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(
+ mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index b41db31..b74da1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -29,6 +29,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
@@ -73,6 +74,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
@@ -1086,6 +1089,39 @@
assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
}
+ @Test
+ public void testRecomputeConfigurationForCameraCompatIfNeeded() {
+ spyOn(mController);
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(false).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+
+ verify(mActivity, never()).recomputeConfiguration();
+
+ // isOverrideOrientationOnlyForCameraEnabled
+ doReturn(true).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // isCameraCompatSplitScreenAspectRatioAllowed
+ doReturn(false).when(mController).isOverrideOrientationOnlyForCameraEnabled();
+ doReturn(true).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+
+ // shouldOverrideMinAspectRatioForCamera
+ doReturn(false).when(mController).isCameraCompatSplitScreenAspectRatioAllowed();
+ doReturn(true).when(mController).shouldOverrideMinAspectRatioForCamera();
+ clearInvocations(mActivity);
+ mController.recomputeConfigurationForCameraCompatIfNeeded();
+ verify(mActivity).recomputeConfiguration();
+ }
+
private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
boolean orientationRequest) {
spyOn(mController);
@@ -1280,6 +1316,78 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mActivity).isCameraActive();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ doReturn(true).when(mActivity).isCameraActive();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatioForCamera());
+ }
+
+ @Test
@EnableCompatChanges({FORCE_RESIZE_APP})
public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
mController = new LetterboxUiController(mWm, mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 48fc2dc..9f85acb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -565,36 +565,17 @@
@Test
public void testGetOrientation_childSpecified() {
- testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE,
- SCREEN_ORIENTATION_LANDSCAPE);
- testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET,
- SCREEN_ORIENTATION_UNSPECIFIED);
- }
-
- private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation,
- int expectedOrientation) {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
- final TestWindowContainer root = builder.setLayer(0).build();
+ final TestWindowContainer root = builder.build();
root.setFillsParent(true);
+ assertEquals(SCREEN_ORIENTATION_UNSET, root.getOrientation());
- builder.setIsVisible(childVisible);
+ final TestWindowContainer child = root.addChildWindow();
+ child.setFillsParent(true);
+ assertEquals(SCREEN_ORIENTATION_UNSET, root.getOrientation());
- if (childOrientation != SCREEN_ORIENTATION_UNSET) {
- builder.setOrientation(childOrientation);
- }
-
- final TestWindowContainer child1 = root.addChildWindow(builder);
- child1.setFillsParent(true);
-
- assertEquals(expectedOrientation, root.getOrientation());
- }
-
- @Test
- public void testGetOrientation_Unset() {
- final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
- final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
- // Unspecified well because we didn't specify anything...
- assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+ child.setOverrideOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a35a35a..9d14290 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1241,7 +1241,7 @@
break;
case Event.SHORTCUT_INVOCATION:
case Event.CHOOSER_ACTION:
- case Event.STANDBY_BUCKET_CHANGED:
+ // case Event.STANDBY_BUCKET_CHANGED:
case Event.FOREGROUND_SERVICE_START:
case Event.FOREGROUND_SERVICE_STOP:
logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 439cf13..1dc1037 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4b6224e..57a58c8 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 583bcb7..2cb86e0 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index d6ae2b3..2cf85fa 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 38442db..b93e1be 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- enable AOD -->
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 4036858..9c6a17d3 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 797ca4e..ecbed28 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index b5ea739..1eacdfd 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -8,6 +8,8 @@
<option name="isolated-storage" value="false"/>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- prevents the phone from restarting -->