Merge "Implement pinch resizing in PiP2" into main
diff --git a/DREAM_MANAGER_OWNERS b/DREAM_MANAGER_OWNERS
new file mode 100644
index 0000000..48bde60
--- /dev/null
+++ b/DREAM_MANAGER_OWNERS
@@ -0,0 +1 @@
+brycelee@google.com
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..f6213b9 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -31,6 +31,7 @@
"&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
srcs: [
+ ":aconfigd_protos",
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 74b34fb..412f2b7 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -104,6 +104,18 @@
],
}
+genrule {
+ name: "framework-minus-apex.ravenwood.keep_all",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":framework-minus-apex.ravenwood-base{hoststubgen_keep_all.txt}",
+ ],
+ out: [
+ "hoststubgen_framework-minus-apex_keep_all.txt",
+ ],
+}
+
java_library {
name: "services.core-for-hoststubgen",
installable: false, // host only jar.
@@ -189,6 +201,18 @@
],
}
+genrule {
+ name: "services.core.ravenwood.keep_all",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":services.core.ravenwood-base{hoststubgen_keep_all.txt}",
+ ],
+ out: [
+ "hoststubgen_services.core_keep_all.txt",
+ ],
+}
+
java_library {
name: "services.core.ravenwood-jarjar",
installable: false,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 384d786..ff73a49 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -80,6 +80,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.storage.StorageManagerInternal;
@@ -179,6 +180,8 @@
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean DEBUG_STANDBY = DEBUG || false;
+ public static final String TRACE_TRACK_NAME = "JobScheduler";
+
/** The maximum number of jobs that we allow an app to schedule */
private static final int MAX_JOBS_PER_APP = 150;
/** The number of the most recently completed jobs to keep track of for debugging purposes. */
@@ -4344,7 +4347,11 @@
final boolean wasConsideredCharging = isConsideredCharging();
mChargingPolicy = newPolicy;
-
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ "CHARGING POLICY CHANGED#" + mChargingPolicy);
+ }
if (isConsideredCharging() != wasConsideredCharging) {
for (int c = mControllers.size() - 1; c >= 0; --c) {
mControllers.get(c).onBatteryStateChangedLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 39d50f5..d65a66c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -535,29 +535,17 @@
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- final String componentPackage = job.getServiceComponent().getPackageName();
- String traceTag = "*job*<" + job.getSourceUid() + ">" + sourcePackage;
- if (!sourcePackage.equals(componentPackage)) {
- traceTag += ":" + componentPackage;
- }
- traceTag += "/" + job.getServiceComponent().getShortClassName();
- if (!componentPackage.equals(job.serviceProcessName)) {
- traceTag += "$" + job.serviceProcessName;
- }
- if (job.getNamespace() != null) {
- traceTag += "@" + job.getNamespace();
- }
- traceTag += "#" + job.getJobId();
-
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
- traceTag, getId());
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME, job.computeSystemTraceTag(),
+ getId());
}
if (job.getAppTraceTag() != null) {
// Use the job's ID to distinguish traces since the ID will be unique per app.
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler",
- job.getAppTraceTag(), job.getJobId());
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP,
+ JobSchedulerService.TRACE_TRACK_NAME, job.getAppTraceTag(),
+ job.getJobId());
}
try {
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
@@ -1605,12 +1593,12 @@
completedJob.getFilteredTraceTag(),
completedJob.getFilteredDebugTags());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
- getId());
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME, getId());
}
if (completedJob.getAppTraceTag() != null) {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler",
- completedJob.getJobId());
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP,
+ JobSchedulerService.TRACE_TRACK_NAME, completedJob.getJobId());
}
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 7fca867..e3af1d8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -572,6 +572,9 @@
/** The reason a job most recently went from ready to not ready. */
private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+ /** The system trace tag for this job. */
+ private String mSystemTraceTag;
+
/**
* Core constructor for JobStatus instances. All other ctors funnel down to this one.
*
@@ -1058,6 +1061,38 @@
return job.getTraceTag();
}
+ /** Returns a trace tag using debug information provided by job scheduler service. */
+ @NonNull
+ public String computeSystemTraceTag() {
+ // Guarded by JobSchedulerService.mLock, no need for synchronization.
+ if (mSystemTraceTag != null) {
+ return mSystemTraceTag;
+ }
+
+ mSystemTraceTag = computeSystemTraceTagInner();
+ return mSystemTraceTag;
+ }
+
+ @NonNull
+ private String computeSystemTraceTagInner() {
+ final String componentPackage = getServiceComponent().getPackageName();
+ StringBuilder traceTag = new StringBuilder(128);
+ traceTag.append("*job*<").append(sourceUid).append(">").append(sourcePackageName);
+ if (!sourcePackageName.equals(componentPackage)) {
+ traceTag.append(":").append(componentPackage);
+ }
+ traceTag.append("/").append(getServiceComponent().getShortClassName());
+ if (!componentPackage.equals(serviceProcessName)) {
+ traceTag.append("$").append(serviceProcessName);
+ }
+ if (mNamespace != null && !mNamespace.trim().isEmpty()) {
+ traceTag.append("@").append(mNamespace);
+ }
+ traceTag.append("#").append(getJobId());
+
+ return traceTag.toString();
+ }
+
/** Returns whether this job was scheduled by one app on behalf of another. */
public boolean isProxyJob() {
return mIsProxyJob;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index c240b3f..a1c72fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -50,6 +50,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -2181,6 +2182,12 @@
}
scheduleCutoff();
}
+ } else {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ "QC/- " + mPkg);
+ }
}
}
@@ -2720,6 +2727,11 @@
if (timeRemainingMs <= 50) {
// Less than 50 milliseconds left. Start process of shutting down jobs.
if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ pkg + "#" + MSG_REACHED_TIME_QUOTA);
+ }
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(),
@@ -2748,6 +2760,11 @@
pkg.userId, pkg.packageName);
if (timeRemainingMs <= 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA);
+ }
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(),
@@ -2772,6 +2789,12 @@
Slog.d(TAG, pkg + " has reached its count quota.");
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ pkg + "#" + MSG_REACHED_COUNT_QUOTA);
+ }
+
mStateChangedListener.onControllerStateChanged(
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(),
@@ -2928,6 +2951,11 @@
}
mTempAllowlistGraceCache.delete(uid);
mTopAppGraceCache.delete(uid);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+ JobSchedulerService.TRACE_TRACK_NAME,
+ "<" + uid + ">#" + MSG_END_GRACE_PERIOD);
+ }
final ArraySet<String> packages = mService.getPackagesForUidLocked(uid);
if (packages != null) {
final int userId = UserHandle.getUserId(uid);
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index 488292d..f726361 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -292,13 +292,17 @@
int childCount = node.getChildCount();
for (int x = 0; x < childCount; x++) {
AccessibilityNodeInfo childNode = node.getChild(x);
-
+ if (childNode == null) {
+ continue;
+ }
if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
- || !safeCharSeqToString(childNode.getText()).isEmpty())
+ || !safeCharSeqToString(childNode.getText()).isEmpty()) {
return true;
+ }
- if (childNafCheck(childNode))
+ if (childNafCheck(childNode)) {
return true;
+ }
}
return false;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 624227d..14ae3f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -4260,6 +4260,12 @@
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
}
+ public final class TaskFragmentParentInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR;
+ }
+
public final class TaskFragmentTransaction implements android.os.Parcelable {
ctor public TaskFragmentTransaction();
method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change);
@@ -4284,8 +4290,8 @@
method @Nullable public android.os.IBinder getActivityToken();
method @NonNull public android.os.Bundle getErrorBundle();
method @Nullable public android.os.IBinder getErrorCallbackToken();
- method @Nullable public android.content.res.Configuration getTaskConfiguration();
method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo();
+ method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo();
method @Nullable public android.os.IBinder getTaskFragmentToken();
method public int getTaskId();
method public int getType();
@@ -4293,7 +4299,6 @@
method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder);
method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle);
method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder);
- method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 7ee3413..497d47a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2015,7 +2015,7 @@
* null for no callback
* @param handler {@link Handler} identifying the callback thread,
* null for the main thread
- * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
* succeeded.
* @hide
*/
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5dd4a7..e1cb630 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5796,6 +5796,8 @@
* @see #onRequestPermissionsResult
*/
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @SuppressLint("OnNameExpected")
+ // Suppress lint as this is an overload of the original API.
public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
: createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9ea55f5..c6a1546 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -103,7 +103,8 @@
@IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
- MODE_BACKGROUND_ACTIVITY_START_DENIED})
+ MODE_BACKGROUND_ACTIVITY_START_DENIED,
+ MODE_BACKGROUND_ACTIVITY_START_COMPAT})
public @interface BackgroundActivityStartMode {}
/**
* No explicit value chosen. The system will decide whether to grant privileges.
@@ -117,6 +118,13 @@
* Deny the {@link PendingIntent} to use the background activity start privileges.
*/
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
+ /**
+ * Special behavior for compatibility.
+ * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}
+ *
+ * @hide
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1;
/**
* The package name that created the options.
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 7724c23..92543b1 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -32,6 +32,11 @@
public boolean topActivityEligibleForLetterboxEducation;
/**
+ * Whether the letterbox education is enabled
+ */
+ public boolean isLetterboxEducationEnabled;
+
+ /**
* Whether the direct top activity is in size compat mode on foreground.
*/
public boolean topActivityInSizeCompat;
@@ -178,6 +183,7 @@
== that.topActivityEligibleForUserAspectRatioButton
&& topActivityEligibleForLetterboxEducation
== that.topActivityEligibleForLetterboxEducation
+ && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
@@ -192,6 +198,7 @@
* Reads the AppCompatTaskInfo from a parcel.
*/
void readFromParcel(Parcel source) {
+ isLetterboxEducationEnabled = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
topActivityEligibleForLetterboxEducation = source.readBoolean();
isLetterboxDoubleTapEnabled = source.readBoolean();
@@ -212,6 +219,7 @@
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(isLetterboxEducationEnabled);
dest.writeBoolean(topActivityInSizeCompat);
dest.writeBoolean(topActivityEligibleForLetterboxEducation);
dest.writeBoolean(isLetterboxDoubleTapEnabled);
@@ -232,6 +240,7 @@
return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat
+ " topActivityEligibleForLetterboxEducation= "
+ topActivityEligibleForLetterboxEducation
+ + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled
+ " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
+ " topActivityEligibleForUserAspectRatioButton= "
+ topActivityEligibleForUserAspectRatioButton
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2d0f6fc..54f6909 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -482,7 +482,8 @@
UID_STATE_FOREGROUND_SERVICE,
UID_STATE_FOREGROUND,
UID_STATE_BACKGROUND,
- UID_STATE_CACHED
+ UID_STATE_CACHED,
+ UID_STATE_NONEXISTENT
})
public @interface UidState {}
@@ -566,6 +567,12 @@
public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED;
/**
+ * Special uid state: The UID is not running
+ * @hide
+ */
+ public static final int UID_STATE_NONEXISTENT = Integer.MAX_VALUE;
+
+ /**
* Resolves the first unrestricted state given an app op.
* @param op The op to resolve.
* @return The last restricted UID state.
@@ -596,6 +603,7 @@
UID_STATE_FOREGROUND,
UID_STATE_BACKGROUND,
UID_STATE_CACHED
+ // UID_STATE_NONEXISTENT isn't a real UID state, so it is excluded
};
/** @hide */
@@ -615,6 +623,8 @@
return "bg";
case UID_STATE_CACHED:
return "cch";
+ case UID_STATE_NONEXISTENT:
+ return "gone";
default:
return "unknown";
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 397477d..0e8e2e3 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -18,6 +18,7 @@
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
@@ -54,7 +55,7 @@
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
- private @Nullable Boolean mPendingIntentBalAllowed = null;
+ private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
private boolean mPendingIntentBalAllowedByPermission = false;
ComponentOptions() {
@@ -65,12 +66,9 @@
// results they want, which is their loss.
opts.setDefusable(true);
- boolean pendingIntentBalAllowedIsSetExplicitly =
- opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- if (pendingIntentBalAllowedIsSetExplicitly) {
- mPendingIntentBalAllowed =
- opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- }
+ mPendingIntentBalAllowed =
+ opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
@@ -85,7 +83,8 @@
* @hide
*/
@Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
- mPendingIntentBalAllowed = allowed;
+ mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ : MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -98,11 +97,8 @@
* @hide
*/
@Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
- if (mPendingIntentBalAllowed == null) {
- // cannot return null, so return the value used up to API level 33 for compatibility
- return true;
- }
- return mPendingIntentBalAllowed;
+ // cannot return all detail, so return the value used up to API level 33 for compatibility
+ return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -119,16 +115,15 @@
@BackgroundActivityStartMode int state) {
switch (state) {
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
- mPendingIntentBalAllowed = null;
- break;
- case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
- mPendingIntentBalAllowed = true;
- break;
case MODE_BACKGROUND_ACTIVITY_START_DENIED:
- mPendingIntentBalAllowed = false;
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ mPendingIntentBalAllowed = state;
break;
default:
- throw new IllegalArgumentException(state + " is not valid");
+ // Assume that future values are some variant of allowing the start.
+ mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ break;
}
return this;
}
@@ -141,13 +136,7 @@
* @see #setPendingIntentBackgroundActivityStartMode(int)
*/
public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
- if (mPendingIntentBalAllowed == null) {
- return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
- } else if (mPendingIntentBalAllowed) {
- return MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return MODE_BACKGROUND_ACTIVITY_START_DENIED;
- }
+ return mPendingIntentBalAllowed;
}
/**
@@ -170,8 +159,8 @@
/** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
- if (mPendingIntentBalAllowed != null) {
- b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+ if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
}
if (mPendingIntentBalAllowedByPermission) {
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 0e20138..cd7f1e4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -679,11 +679,13 @@
if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (mCancelable) {
cancel();
- } else {
+ event.startTracking();
+ return true;
+ } else if (mWindow.shouldCloseOnTouchOutside()) {
dismiss();
+ event.startTracking();
+ return true;
}
- event.startTracking();
- return true;
}
return false;
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index ef6982e..4ac40a1 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
@@ -30,6 +31,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.DreamService;
+import android.service.dreams.Flags;
import android.service.dreams.IDreamManager;
/**
@@ -217,4 +219,19 @@
}
return false;
}
+
+ /**
+ * Sets whether the dream is obscured by something.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED)
+ @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+ public void setDreamIsObscured(boolean isObscured) {
+ try {
+ mService.setDreamIsObscured(isObscured);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9c80659..0672064 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5704,6 +5704,7 @@
p.headerless(resId == getBaseLayoutResource()
|| resId == getHeadsUpBaseLayoutResource()
|| resId == getCompactHeadsUpBaseLayoutResource()
+ || resId == getMessagingCompactHeadsUpLayoutResource()
|| resId == getMessagingLayoutResource()
|| resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
@@ -6491,8 +6492,13 @@
// visual regressions.
@SuppressWarnings("AndroidFrameworkCompatChange")
private boolean bigContentViewRequired() {
- if (!Flags.notificationExpansionOptional()
- && mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
+ if (Flags.notificationExpansionOptional()) {
+ // Notifications without a bigContentView, style, or actions do not need to expand
+ boolean exempt = mN.bigContentView == null
+ && mStyle == null && mActions.size() == 0;
+ return !exempt;
+ }
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
return true;
}
// Notifications with contentView and without a bigContentView, style, or actions would
@@ -7294,6 +7300,10 @@
return R.layout.notification_template_material_compact_heads_up_base;
}
+ private int getMessagingCompactHeadsUpLayoutResource() {
+ return R.layout.notification_template_material_messaging_compact_heads_up;
+ }
+
private int getBigBaseLayoutResource() {
return R.layout.notification_template_material_big_base;
}
@@ -9166,10 +9176,78 @@
@Nullable
@Override
public RemoteViews makeCompactHeadsUpContentView() {
- // TODO(b/336229954): Apply minimal HUN treatment to Messaging Notifications.
- return makeHeadsUpContentView(false);
+ final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
+ Icon conversationIcon = null;
+ Notification.Action remoteInputAction = null;
+ if (isConversationLayout) {
+
+ conversationIcon = mShortcutIcon;
+
+ // conversation icon is m
+ // Extract the conversation icon for one to one conversations from
+ // the latest incoming message since
+ // fixTitleAndTextExtras also uses it as data source for title and text
+ if (conversationIcon == null && !mIsGroupConversation) {
+ final Message message = findLatestIncomingMessage();
+ if (message != null) {
+ final Person sender = message.mSender;
+ if (sender != null) {
+ conversationIcon = sender.getIcon();
+ }
+ }
+ }
+
+ if (Flags.compactHeadsUpNotificationReply()) {
+ // Get the first non-contextual inline reply action.
+ final List<Notification.Action> nonContextualActions =
+ mBuilder.getNonContextualActions();
+ for (int i = 0; i < nonContextualActions.size(); i++) {
+ final Notification.Action action = nonContextualActions.get(i);
+ if (mBuilder.hasValidRemoteInput(action)) {
+ remoteInputAction = action;
+ break;
+ }
+ }
+ }
+ }
+
+ // This method fills title and text
+ fixTitleAndTextExtras(mBuilder.mN.extras);
+ final StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+ .highlightExpander(isConversationLayout)
+ .fillTextsFrom(mBuilder)
+ .hideTime(true)
+ .summaryText("");
+ p.headerTextSecondary(p.mText);
+ TemplateBindResult bindResult = new TemplateBindResult();
+
+ RemoteViews contentView = mBuilder.applyStandardTemplate(
+ mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult);
+ if (conversationIcon != null) {
+ contentView.setViewVisibility(R.id.icon, View.GONE);
+ contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE);
+ contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true);
+ contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon);
+ }
+
+ if (remoteInputAction != null) {
+ contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE);
+
+ final RemoteViews inlineReplyButton =
+ mBuilder.generateActionButton(remoteInputAction, false, p);
+ // Clear the drawable
+ inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0);
+ inlineReplyButton.setTextViewText(R.id.action0,
+ mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply));
+ contentView.addView(R.id.reply_action_container, inlineReplyButton);
+ } else {
+ contentView.setViewVisibility(R.id.reply_action_container, View.GONE);
+ }
+ return contentView;
}
+
/**
* @hide
*/
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index b82a1e3..e4310c1 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2972,6 +2972,7 @@
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.ACCESS_NOTIFICATIONS})
@FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ @SuppressLint("UserHandle")
public void registerCallNotificationEventListener(@NonNull String packageName,
@NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
@NonNull CallNotificationEventListener listener) {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 97c2e43..2e38c06 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -60,6 +60,9 @@
# ComponentCaller
per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS
+# DreamManager
+per-file DreamManager.java = file:/DREAM_MANAGER_OWNERS
+
# GrammaticalInflectionManager
per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a29c196..dc44764 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -392,6 +392,14 @@
{
"file_patterns": ["(/|^)AppOpsManager.java"],
"name": "CtsAppOpsTestCases"
+ },
+ {
+ "file_patterns": [
+ "(/|^)Activity.*.java",
+ "(/|^)PendingIntent.java",
+ "(/|^)ComtextImpl.java"
+ ],
+ "name": "CtsWindowManagerBackgroundActivityTestCases"
}
]
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 69f29f3..4bc1ce6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10427,7 +10427,7 @@
@WorkerThread
public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
Bundle settings) {
- if (!Flags.dmrhCanSetAppRestriction()) {
+ if (!Flags.dmrhSetAppRestrictions()) {
throwIfParentInstance("setApplicationRestrictions");
}
@@ -11835,7 +11835,7 @@
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@Nullable ComponentName admin, String packageName) {
- if (!Flags.dmrhCanSetAppRestriction()) {
+ if (!Flags.dmrhSetAppRestrictions()) {
throwIfParentInstance("getApplicationRestrictions");
}
@@ -14120,7 +14120,7 @@
public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
throwIfParentInstance("getParentProfileInstance");
try {
- if (Flags.dmrhCanSetAppRestriction()) {
+ if (Flags.dmrhSetAppRestrictions()) {
UserManager um = mContext.getSystemService(UserManager.class);
if (!um.isManagedProfile()) {
throw new SecurityException("The current user does not have a parent profile.");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3d6ec19..4154e66 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -244,10 +244,13 @@
}
flag {
- name: "dmrh_can_set_app_restriction"
+ name: "dmrh_set_app_restrictions"
namespace: "enterprise"
description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
bug: "328758346"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 50c7b7f..6ceae17 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -160,3 +160,10 @@
description: "[Minimal HUN] Enables the compact heads up notification feature"
bug: "270709257"
}
+
+flag {
+ name: "compact_heads_up_notification_reply"
+ namespace: "systemui"
+ description: "[Minimal HUN] Enables the compact heads up notification reply capability for Conversation Notifications"
+ bug: "336229954"
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 5e1c1e0..a37f51b 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -529,7 +529,6 @@
* {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
* when passed via Binder IPC. Following restrictions apply :
* <ul>
- * <li> No Nested Bundles are allowed.</li>
* <li> {@link PersistableBundle}s are allowed.</li>
* <li> Any primitive types or their collections can be added as usual.</li>
* <li>IBinder objects should *not* be added.</li>
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index cda2867..9b53461 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -33,11 +33,13 @@
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.window.ActivityWindowInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer;
/**
@@ -47,6 +49,8 @@
*/
public class ClientTransactionListenerController {
+ private static final String TAG = "ClientTransactionListenerController";
+
private static ClientTransactionListenerController sController;
private final Object mLock = new Object();
@@ -179,10 +183,14 @@
}
// Dispatch the display changed callbacks.
- final int displayCount = configUpdatedDisplayIds.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplayIds.valueAt(i);
- onDisplayChanged(displayId);
+ try {
+ final int displayCount = configUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = configUpdatedDisplayIds.valueAt(i);
+ onDisplayChanged(displayId);
+ }
+ } catch (RejectedExecutionException e) {
+ Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
}
}
@@ -222,7 +230,11 @@
}
if (changedDisplayId != INVALID_DISPLAY) {
- onDisplayChanged(changedDisplayId);
+ try {
+ onDisplayChanged(changedDisplayId);
+ } catch (RejectedExecutionException e) {
+ Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
+ }
}
}
@@ -235,9 +247,11 @@
/**
* Called when receives a {@link Configuration} changed event that is updating display-related
* window configuration.
+ *
+ * @throws RejectedExecutionException if the display listener handler is closing.
*/
@VisibleForTesting
- public void onDisplayChanged(int displayId) {
+ public void onDisplayChanged(int displayId) throws RejectedExecutionException {
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 57b5c13..3213b40 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1041,6 +1041,7 @@
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
if (mService == null) {
+ Log.e(TAG, "Service wasn't initialized, appWidgetId=" + appWidgetId);
return null;
}
try {
@@ -1048,6 +1049,9 @@
if (info != null) {
// Converting complex to dp.
info.updateDimensions(mDisplayMetrics);
+ } else {
+ Log.e(TAG, "App widget provider info is null. PackageName=" + mPackageName
+ + " appWidgetId-" + appWidgetId);
}
return info;
} catch (RemoteException e) {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 8458857..36d0e08 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -39,3 +39,11 @@
description: "Expose perm sync user consent API"
bug: "309528663"
}
+
+flag {
+ name: "ongoing_perm_sync"
+ is_exported: true
+ namespace: "companion"
+ description: "Enable ongoing perm sync"
+ bug: "338469649"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3e23762..b29b52d 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -127,4 +127,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "impulse_velocity_strategy_for_touch_navigation"
+ is_exported: true
+ namespace: "virtual_devices"
+ description: "Use impulse velocity strategy during conversion of touch navigation flings into Dpad events"
+ bug: "338426241"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b074e8b..2e252c1 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -60,6 +60,10 @@
* {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
*/
public class IntentSender implements Parcelable {
+ private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
+
@UnsupportedAppUsage
private final IIntentSender mTarget;
IBinder mWhitelistToken;
@@ -161,7 +165,8 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
+ sendIntent(context, code, intent, onFinished, handler, null,
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
@@ -194,7 +199,7 @@
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
sendIntent(context, code, intent, onFinished, handler, requiredPermission,
- null /* options */);
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2cfbf5..f08395a 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -63,5 +63,11 @@
"name": "CtsContentTestCasesRavenwood",
"host": true
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsWindowManagerBackgroundActivityTestCases",
+ "file_patterns": ["(/|^)IntentSender.java"]
+ }
]
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 83285e0..c506c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -270,11 +270,20 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for a app to inform the installer that a file containing the app's android
- * safety label data is bundled into the APK at the given path.
+ * safety label data is bundled into the APK as a raw resource.
+ *
+ * <p>For example:
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL"
+ * android:resource="@raw/app-metadata"/>
+ * </application>
+ * </pre>
* @hide
*/
- public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH =
- "android.content.SAFETY_LABEL_PATH";
+ public static final String PROPERTY_ANDROID_SAFETY_LABEL =
+ "android.content.PROPERTY_ANDROID_SAFETY_LABEL";
/**
* A property value set within the manifest.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4b579e7..1f6730b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2628,6 +2628,15 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
@@ -2699,6 +2708,15 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index cee8d96..061e7f7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -254,6 +254,14 @@
}
flag {
+ name: "wait_application_killed"
+ namespace: "package_manager_service"
+ description: "Feature flag to control whether to wait until the application is killed when clear application data"
+ bug: "31009094"
+ is_fixed_read_only: true
+}
+
+flag {
name: "component_state_changed_metrics"
namespace: "package_manager_service"
description: "Feature flag to log the metrics when the component state is changed."
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 83742eb..e2a131c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -247,3 +247,13 @@
description: "Allow MAIN user to access blocked number provider"
bug: "338579331"
}
+
+flag {
+ name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles"
+ namespace: "profile_experiences"
+ description: "Use user states to check the state of quiet mode for managed profiles only"
+ bug: "332812630"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 153dd9a..c7403c0 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -316,6 +316,15 @@
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Parsed package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -368,19 +377,27 @@
return input.success(targetVers);
}
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (matchTargetCode(platformSdkCodenames, targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Parsed package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
try {
if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
} catch (IllegalArgumentException e) {
- // isAtMost() throws it when encountering an older SDK codename
- return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage());
- }
-
- // If it's a pre-release SDK and the codename matches this platform, it
- // definitely targets this SDK.
- if (matchTargetCode(platformSdkCodenames, targetCode)) {
- return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
}
// Otherwise, we're looking at an incompatible pre-release SDK.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0e40f6..61f1ee1 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -34,6 +34,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
@@ -603,7 +604,6 @@
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
- // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Set if emergency call button should show, for example if biometrics are
@@ -613,12 +613,33 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) {
mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton);
return this;
}
/**
+ * Set caller's component name for getting logo icon/description. This should only be used
+ * by ConfirmDeviceCredentialActivity, see b/337082634 for more context.
+ *
+ * @param componentNameForConfirmDeviceCredentialActivity set the component name for
+ * ConfirmDeviceCredentialActivity.
+ * @return This builder.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
+ public Builder setComponentNameForConfirmDeviceCredentialActivity(
+ ComponentName componentNameForConfirmDeviceCredentialActivity) {
+ mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity(
+ componentNameForConfirmDeviceCredentialActivity);
+ return this;
+ }
+
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
+
+ /**
* Creates a {@link BiometricPrompt}.
*
* @return An instance of {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 18b75c9..bb07b9b 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -19,6 +19,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
@@ -56,6 +57,7 @@
private boolean mIsForLegacyFingerprintManager = false;
private boolean mShowEmergencyCallButton = false;
private boolean mUseParentProfileForDeviceCredential = false;
+ private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null;
public PromptInfo() {
@@ -87,6 +89,8 @@
mIsForLegacyFingerprintManager = in.readBoolean();
mShowEmergencyCallButton = in.readBoolean();
mUseParentProfileForDeviceCredential = in.readBoolean();
+ mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable(
+ ComponentName.class.getClassLoader(), ComponentName.class);
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -132,10 +136,11 @@
dest.writeBoolean(mIsForLegacyFingerprintManager);
dest.writeBoolean(mShowEmergencyCallButton);
dest.writeBoolean(mUseParentProfileForDeviceCredential);
+ dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0);
}
// LINT.IfChange
- public boolean containsTestConfigurations() {
+ public boolean requiresTestOrInternalPermission() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
&& !mAllowBackgroundAuthentication) {
@@ -148,11 +153,15 @@
return true;
} else if (mIgnoreEnrollmentState) {
return true;
+ } else if (mShowEmergencyCallButton) {
+ return true;
+ } else if (mComponentNameForConfirmDeviceCredentialActivity != null) {
+ return true;
}
return false;
}
- public boolean containsPrivateApiConfigurations() {
+ public boolean requiresInternalPermission() {
if (mDisallowBiometricsIfPolicyExists) {
return true;
} else if (mUseDefaultTitle) {
@@ -177,7 +186,7 @@
* Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs
* this permission.
*/
- public boolean containsAdvancedApiConfigurations() {
+ public boolean requiresAdvancedPermission() {
if (mLogoRes != -1) {
return true;
} else if (mLogoBitmap != null) {
@@ -305,6 +314,12 @@
mShowEmergencyCallButton = showEmergencyCallButton;
}
+ public void setComponentNameForConfirmDeviceCredentialActivity(
+ ComponentName componentNameForConfirmDeviceCredentialActivity) {
+ mComponentNameForConfirmDeviceCredentialActivity =
+ componentNameForConfirmDeviceCredentialActivity;
+ }
+
public void setUseParentProfileForDeviceCredential(
boolean useParentProfileForDeviceCredential) {
mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential;
@@ -417,6 +432,10 @@
return mShowEmergencyCallButton;
}
+ public ComponentName getComponentNameForConfirmDeviceCredentialActivity() {
+ return mComponentNameForConfirmDeviceCredentialActivity;
+ }
+
private void checkOnlyOneLogoSet() {
if (mLogoRes != -1 && mLogoBitmap != null) {
throw new IllegalStateException(
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2b7d8f1..708f8a1 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -39,7 +39,6 @@
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.hardware.CameraExtensionSessionStats;
-import android.hardware.CameraIdRemapping;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -1996,17 +1995,6 @@
}
/**
- * Remaps Camera Ids in the CameraService.
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
- public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
- throws CameraAccessException, SecurityException, IllegalArgumentException {
- CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
- }
-
- /**
* Injects session params into existing clients in the CameraService.
*
* @param cameraId The camera id of client to inject session params into.
@@ -2117,13 +2105,6 @@
private final Object mLock = new Object();
- /**
- * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
- * in the CameraService every time we connect to it, including when the CameraService
- * Binder dies and we reconnect to it.
- */
- @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
-
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
@@ -2275,41 +2256,6 @@
} catch (RemoteException e) {
// Camera service died in all probability
}
-
- if (mActiveCameraIdRemapping != null) {
- try {
- cameraService.remapCameraIds(mActiveCameraIdRemapping);
- } catch (ServiceSpecificException e) {
- // Unexpected failure, ignore and continue.
- Log.e(TAG, "Unable to remap camera Ids in the camera service");
- } catch (RemoteException e) {
- // Camera service died in all probability
- }
- }
- }
-
- /** Updates the cameraIdRemapping state in the CameraService. */
- public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
- throws CameraAccessException, SecurityException {
- synchronized (mLock) {
- ICameraService cameraService = getCameraService();
- if (cameraService == null) {
- throw new CameraAccessException(
- CameraAccessException.CAMERA_DISCONNECTED,
- "Camera service is currently unavailable.");
- }
-
- try {
- cameraService.remapCameraIds(cameraIdRemapping);
- mActiveCameraIdRemapping = cameraIdRemapping;
- } catch (ServiceSpecificException e) {
- throw ExceptionUtils.throwAsPublicException(e);
- } catch (RemoteException e) {
- throw new CameraAccessException(
- CameraAccessException.CAMERA_DISCONNECTED,
- "Camera service is currently unavailable.");
- }
- }
}
/** Injects session params into an existing client for cameraid. */
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 7754e32..de26384 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2359,7 +2359,10 @@
* FPS.</p>
* <p>If the session configuration is not supported, the AE mode reported in the
* CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
- * <p>The application can observe the CapturerResult field
+ * <p>When this AE mode is enabled, the CaptureResult field
+ * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be present and not null. Otherwise, the
+ * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} field will not be present in the CaptureResult.</p>
+ * <p>The application can observe the CaptureResult field
* {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
* 'INACTIVE'.</p>
* <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 5765a73..1460515 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2819,6 +2819,8 @@
* <p>When low light boost is enabled by setting the AE mode to
* 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
* boost when the light level threshold is exceeded.</p>
+ * <p>This field is present in the CaptureResult when the AE mode is set to
+ * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p>
* <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
* indicate when it is not being applied by returning 'INACTIVE'.</p>
* <p>This key will be absent from the CaptureResult if AE mode is not set to
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index d340f3f..3f2ef84 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -732,6 +732,10 @@
/**
* Get statically configured sensor properties.
+ * @deprecated Generally unsafe to use, use
+ * {@link FaceManager#addAuthenticatorsRegisteredCallback} API instead.
+ * In most cases this method will work as expected, but during early boot up, it will be
+ * null/empty and there is no way for the caller to know when it's actual value is ready.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 25bfb2a..2ded615 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1189,6 +1189,10 @@
/**
* Get statically configured sensor properties.
+ * @deprecated Generally unsafe to use, use
+ * {@link FingerprintManager#addAuthenticatorsRegisteredCallback} API instead.
+ * In most cases this method will work as expected, but during early boot up, it will be
+ * null/empty and there is no way for the caller to know when it's actual value is ready.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 66b0a42..3a271b4 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.usb.flags.Flags;
import android.service.usb.UsbDeviceFilterProto;
import android.util.Slog;
@@ -57,9 +58,12 @@
public final String mProductName;
// USB device serial number string (or null for unspecified)
public final String mSerialNumber;
+ // USB interface name (or null for unspecified). This will be used when matching devices using
+ // the available interfaces.
+ public final String mInterfaceName;
public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum) {
+ String manufacturer, String product, String serialnum, String interfaceName) {
mVendorId = vid;
mProductId = pid;
mClass = clasz;
@@ -68,6 +72,7 @@
mManufacturerName = manufacturer;
mProductName = product;
mSerialNumber = serialnum;
+ mInterfaceName = interfaceName;
}
public DeviceFilter(UsbDevice device) {
@@ -79,6 +84,7 @@
mManufacturerName = device.getManufacturerName();
mProductName = device.getProductName();
mSerialNumber = device.getSerialNumber();
+ mInterfaceName = null;
}
public DeviceFilter(@NonNull DeviceFilter filter) {
@@ -90,6 +96,7 @@
mManufacturerName = filter.mManufacturerName;
mProductName = filter.mProductName;
mSerialNumber = filter.mSerialNumber;
+ mInterfaceName = filter.mInterfaceName;
}
public static DeviceFilter read(XmlPullParser parser)
@@ -102,7 +109,7 @@
String manufacturerName = null;
String productName = null;
String serialNumber = null;
-
+ String interfaceName = null;
int count = parser.getAttributeCount();
for (int i = 0; i < count; i++) {
String name = parser.getAttributeName(i);
@@ -114,6 +121,8 @@
productName = value;
} else if ("serial-number".equals(name)) {
serialNumber = value;
+ } else if ("interface-name".equals(name)) {
+ interfaceName = value;
} else {
int intValue;
int radix = 10;
@@ -144,7 +153,7 @@
}
return new DeviceFilter(vendorId, productId,
deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber);
+ manufacturerName, productName, serialNumber, interfaceName);
}
public void write(XmlSerializer serializer) throws IOException {
@@ -173,13 +182,25 @@
if (mSerialNumber != null) {
serializer.attribute(null, "serial-number", mSerialNumber);
}
+ if (mInterfaceName != null) {
+ serializer.attribute(null, "interface-name", mInterfaceName);
+ }
serializer.endTag(null, "usb-device");
}
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass) &&
- (mSubclass == -1 || subclass == mSubclass) &&
- (mProtocol == -1 || protocol == mProtocol));
+ private boolean matches(int usbClass, int subclass, int protocol) {
+ return ((mClass == -1 || usbClass == mClass)
+ && (mSubclass == -1 || subclass == mSubclass)
+ && (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) {
+ if (Flags.enableInterfaceNameDeviceFilter()) {
+ return matches(usbClass, subclass, protocol)
+ && (mInterfaceName == null || mInterfaceName.equals(interfaceName));
+ } else {
+ return matches(usbClass, subclass, protocol);
+ }
}
public boolean matches(UsbDevice device) {
@@ -204,7 +225,7 @@
for (int i = 0; i < count; i++) {
UsbInterface intf = device.getInterface(i);
if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) return true;
+ intf.getInterfaceProtocol(), intf.getName())) return true;
}
return false;
@@ -320,11 +341,12 @@
@Override
public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
- ",mClass=" + mClass + ",mSubclass=" + mSubclass +
- ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
- ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
- "]";
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+ + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+ + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+ + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber
+ + ",mInterfaceName=" + mInterfaceName
+ + "]";
}
/**
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df160..40e5ffb 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -16,3 +16,11 @@
description: "Feature flag for the api to check if a port supports mode change"
bug: "323470419"
}
+
+flag {
+ name: "enable_interface_name_device_filter"
+ is_exported: true
+ namespace: "usb"
+ description: "Feature flag to enable interface name as a parameter for device filter"
+ bug: "312828160"
+}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 594ec18..334b231 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -173,6 +173,12 @@
public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
/** @hide */
public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_METERED_ALLOW = "metered_allow";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_METERED_DENY_USER = "metered_deny_user";
+ /** @hide */
+ public static final String FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN = "metered_deny_admin";
private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 05a3e18..fedc97d 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1387,7 +1387,11 @@
* @param scheme name or {@code null} if this is a relative Uri
*/
public Builder scheme(String scheme) {
- this.scheme = scheme;
+ if (scheme != null) {
+ this.scheme = scheme.replaceAll("://", "");
+ } else {
+ this.scheme = null;
+ }
return this;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d45a17f..91ad22f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -120,6 +120,8 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* StorageManager is the interface to the systems storage service. The storage
@@ -2512,6 +2514,9 @@
return userId * PER_USER_RANGE + projectId;
}
+ private static final Pattern PATTERN_USER_ID = Pattern.compile(
+ "(?i)^/storage/emulated/([0-9]+)");
+
/**
* Let StorageManager know that the quota type for a file on external storage should
* be updated. Android tracks quotas for various media types. Consequently, this should be
@@ -2541,26 +2546,35 @@
@SystemApi
public void updateExternalStorageFileQuotaType(@NonNull File path,
@QuotaType int quotaType) throws IOException {
+ if (!path.exists()) return;
+
long projectId;
final String filePath = path.getCanonicalPath();
- int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
- // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
- // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
- if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
- volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
- }
- final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
- final StorageVolume volume = getStorageVolume(availableVolumes, path);
- if (volume == null) {
- Log.w(TAG, "Failed to update quota type for " + filePath);
- return;
- }
- if (!volume.isEmulated()) {
- // We only support quota tracking on emulated filesystems
- return;
+
+ final int userId;
+ final Matcher matcher = PATTERN_USER_ID.matcher(filePath);
+ if (matcher.find()) {
+ userId = Integer.parseInt(matcher.group(1));
+ } else { // fallback
+ int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+ // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are
+ // also returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+ if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+ volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+ }
+ final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
+ final StorageVolume volume = getStorageVolume(availableVolumes, path);
+ if (volume == null) {
+ Log.w(TAG, "Failed to update quota type for " + filePath);
+ return;
+ }
+ if (!volume.isEmulated()) {
+ // We only support quota tracking on emulated filesystems
+ return;
+ }
+ userId = volume.getOwner().getIdentifier();
}
- final int userId = volume.getOwner().getIdentifier();
if (userId < 0) {
throw new IllegalStateException("Failed to update quota type for " + filePath);
}
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d2f4b50..857bacd 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,14 +1,13 @@
# Bug component: 137825
-augale@google.com
evanseverson@google.com
fayey@google.com
jaysullivan@google.com
joecastro@google.com
-kvakil@google.com
mrulhania@google.com
ntmyren@google.com
rmacgregor@google.com
theianchen@google.com
yutingfang@google.com
zhanghai@google.com
+kiranmr@google.com
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 0e28560..2ca58d1 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -166,6 +166,16 @@
}
flag {
+ name: "finish_running_ops_for_killed_packages"
+ namespace: "permissions"
+ description: "Finish all appops for a dead app process"
+ bug: "234630570"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "runtime_permission_appops_mapping_enabled"
is_fixed_read_only: true
namespace: "permissions"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 009713f..4f5b67c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11173,6 +11173,35 @@
"visual_query_accessibility_detection_enabled";
/**
+ * Timeout to be used for unbinding to the configured remote
+ * {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no
+ * requests in the queue. A value of -1 represents to never unbind.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS =
+ "on_device_intelligence_unbind_timeout_ms";
+
+
+ /**
+ * Timeout that represents maximum idle time before which a callback should be populated.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS =
+ "on_device_intelligence_idle_timeout_ms";
+
+ /**
+ * Timeout to be used for unbinding to the configured remote
+ * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there
+ * are no requests in the queue. A value of -1 represents to never unbind.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS =
+ "on_device_inference_unbind_timeout_ms";
+
+ /**
* Control whether Night display is currently activated.
* @hide
*/
@@ -14909,6 +14938,17 @@
public static final String DROPBOX_TAG_PREFIX = "dropbox:";
/**
+ * Lines of kernel logs to include with system crash/ANR/etc. reports, as a
+ * prefix of the dropbox tag of the report type. For example,
+ * "kernel_logs_for_system_server_anr" controls the lines of kernel logs
+ * captured with system server ANR reports. 0 to disable.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ERROR_KERNEL_LOG_PREFIX = "kernel_logs_for_";
+
+ /**
* Lines of logcat to include with system crash/ANR/etc. reports, as a
* prefix of the dropbox tag of the report type. For example,
* "logcat_for_system_server_anr" controls the lines of logcat captured
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index d5ac7a7..2eb285d 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -8,6 +8,9 @@
}
]
},
+ {
+ "name": "CtsMediaProviderTestCases"
+ },
{
"name": "CalendarProviderTests"
},
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 5f6bdbf..38ab590 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,7 +18,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
-import static android.service.dreams.Flags.dreamTracksFocus;
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import android.annotation.FlaggedApi;
import android.annotation.IdRes;
@@ -571,15 +571,6 @@
/** {@inheritDoc} */
@Override
public void onWindowFocusChanged(boolean hasFocus) {
- if (!dreamTracksFocus()) {
- return;
- }
-
- try {
- mDreamManager.onDreamFocusChanged(hasFocus);
- } catch (RemoteException ex) {
- // system server died
- }
}
/** {@inheritDoc} */
@@ -1737,7 +1728,7 @@
@Override
public void comeToFront() {
- if (!dreamTracksFocus()) {
+ if (!dreamHandlesBeingObscured()) {
return;
}
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 85f0368..cf98bfe0 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -48,5 +48,6 @@
void setSystemDreamComponent(in ComponentName componentName);
void registerDreamOverlayService(in ComponentName componentName);
void startDreamActivity(in Intent intent);
- void onDreamFocusChanged(in boolean hasFocus);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)")
+ oneway void setDreamIsObscured(in boolean isObscured);
}
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index a42eaff..54d950c 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -39,8 +39,11 @@
}
flag {
- name: "dream_tracks_focus"
+ name: "dream_handles_being_obscured"
namespace: "communal"
- description: "This flag enables the ability for dreams to track whether or not they have focus"
- bug: "331798001"
+ description: "This flag enables the ability for dreams to handle being obscured"
+ bug: "337302237"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 76889df..88da8eb 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -37,9 +38,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
-
import com.android.internal.os.SomeArgs;
-
import java.lang.annotation.Retention;
import java.util.List;
@@ -116,6 +115,7 @@
*/
protected Handler mHandler;
+ @SuppressLint("OnNameExpected")
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 07367df..caa0a9c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Flags;
import android.app.RemoteInput;
@@ -229,6 +230,7 @@
/**
* Records that the user has replied to a notification that has a smart reply at least once.
*/
+ @SuppressLint("GetterSetterNames")
@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void setSmartReplied() {
mSmartReplied = true;
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 1d7091c..910c462 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1007,6 +1007,7 @@
/**
* Set whether priority channels are permitted to break through DND.
*/
+ @SuppressLint("BuilderSetStyle")
@FlaggedApi(Flags.FLAG_MODES_API)
public @NonNull Builder allowPriorityChannels(boolean allow) {
mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 29a6db6..8237b20 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -105,6 +105,21 @@
public static final String SERVICE_INTERFACE =
"android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
+ // TODO(339594686): make API
+ /**
+ * @hide
+ */
+ public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY =
+ "register_model_update_callback";
+ /**
+ * @hide
+ */
+ public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded";
+ /**
+ * @hide
+ */
+ public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded";
+
private IRemoteStorageService mRemoteStorageService;
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d174bef..9589785 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1025,7 +1025,8 @@
mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
: Math.max(mDefaultDimAmount, mCustomDimAmount);
- if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+ if (!ENABLE_WALLPAPER_DIMMING
+ || mBbqSurfaceControl == null || !mBbqSurfaceControl.isValid()
|| mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
return;
}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
index b9ab82c..4de7b62 100644
--- a/core/java/android/tracing/perfetto/DataSource.java
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -85,7 +85,7 @@
new TracingContext<>(this, instanceIndex);
fun.trace(ctx);
- ctx.flush();
+ nativeWritePackets(mNativeObj, ctx.getAndClearAllPendingTracePackets());
} while (nativePerfettoDsTraceIterateNext(mNativeObj));
} finally {
nativePerfettoDsTraceIterateBreak(mNativeObj);
@@ -130,7 +130,8 @@
* @param params Params to initialize the datasource with.
*/
public void register(DataSourceParams params) {
- nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+ nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy,
+ params.willNotifyOnStop, params.noFlush);
}
/**
@@ -163,8 +164,8 @@
return this.createInstance(inputStream, instanceIndex);
}
- private static native void nativeRegisterDataSource(
- long dataSourcePtr, int bufferExhaustedPolicy);
+ private static native void nativeRegisterDataSource(long dataSourcePtr,
+ int bufferExhaustedPolicy, boolean willNotifyOnStop, boolean noFlush);
private static native long nativeCreate(DataSource thiz, String name);
private static native void nativeFlushAll(long nativeDataSourcePointer);
@@ -179,4 +180,6 @@
private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr);
private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr);
private static native int nativeGetPerfettoDsInstanceIndex(long dataSourcePtr);
+
+ private static native void nativeWritePackets(long dataSourcePtr, byte[][] packetData);
}
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
index 6cd04e3..e50f9d7 100644
--- a/core/java/android/tracing/perfetto/DataSourceParams.java
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -46,12 +46,67 @@
// after a while.
public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
- public static DataSourceParams DEFAULTS =
- new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+ public static DataSourceParams DEFAULTS = new DataSourceParams.Builder().build();
- public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+ private DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy,
+ boolean willNotifyOnStop, boolean noFlush) {
this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+ this.willNotifyOnStop = willNotifyOnStop;
+ this.noFlush = noFlush;
}
public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+ public final boolean willNotifyOnStop;
+ public final boolean noFlush;
+
+ /**
+ * DataSource Parameters builder
+ *
+ * @hide
+ */
+ public static final class Builder {
+ /**
+ * Specify behavior when running out of shared memory buffer space.
+ */
+ public Builder setBufferExhaustedPolicy(@PerfettoDsBufferExhausted int value) {
+ this.mBufferExhaustedPolicy = value;
+ return this;
+ }
+
+ /**
+ * If true, the data source is expected to ack the stop request through the
+ * NotifyDataSourceStopped() IPC. If false, the service won't wait for an ack.
+ * Set this parameter to false when dealing with potentially frozen producers
+ * that wouldn't be able to quickly ack the stop request.
+ *
+ * Default value: true
+ */
+ public Builder setWillNotifyOnStop(boolean value) {
+ this.mWillNotifyOnStop = value;
+ return this;
+ }
+
+ /**
+ * If true, the service won't emit flush requests for this data source. This
+ * allows the service to reduce the flush-related IPC traffic and better deal
+ * with frozen producers (see go/perfetto-frozen).
+ */
+ public Builder setNoFlush(boolean value) {
+ this.mNoFlush = value;
+ return this;
+ }
+
+ /**
+ * Build the DataSource parameters.
+ */
+ public DataSourceParams build() {
+ return new DataSourceParams(
+ this.mBufferExhaustedPolicy, this.mWillNotifyOnStop, this.mNoFlush);
+ }
+
+ private @PerfettoDsBufferExhausted int mBufferExhaustedPolicy =
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP;
+ private boolean mWillNotifyOnStop = true;
+ private boolean mNoFlush = false;
+ }
}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 6b7df54..98cb4c8 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -59,19 +59,6 @@
}
/**
- * Forces a commit of the thread-local tracing data written so far to the
- * service. This is almost never required (tracing data is periodically
- * committed as trace pages are filled up) and has a non-negligible
- * performance hit (requires an IPC + refresh of the current thread-local
- * chunk). The only case when this should be used is when handling OnStop()
- * asynchronously, to ensure sure that the data is committed before the
- * Stop timeout expires.
- */
- public void flush() {
- nativeFlush(mDataSource.mNativeObj, getAndClearAllPendingTracePackets());
- }
-
- /**
* Can optionally be used to store custom per-sequence
* session data, which is not reset when incremental state is cleared
* (e.g. configuration options).
@@ -109,7 +96,7 @@
return incrementalState;
}
- private byte[][] getAndClearAllPendingTracePackets() {
+ protected byte[][] getAndClearAllPendingTracePackets() {
byte[][] res = new byte[mTracePackets.size()][];
for (int i = 0; i < mTracePackets.size(); i++) {
ProtoOutputStream tracePacket = mTracePackets.get(i);
@@ -120,8 +107,6 @@
return res;
}
- private static native void nativeFlush(long dataSourcePtr, byte[][] packetData);
-
private static native Object nativeGetCustomTls(long nativeDsPtr);
private static native void nativeSetCustomTls(long nativeDsPtr, Object tlsState);
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 35b137a..c5b6aa7 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -129,7 +129,7 @@
}
ViewGroup effective = null;
ViewParent nextParent = focused.getParent();
- do {
+ while (nextParent instanceof ViewGroup) {
if (nextParent == root) {
return effective != null ? effective : root;
}
@@ -143,7 +143,7 @@
effective = vg;
}
nextParent = nextParent.getParent();
- } while (nextParent instanceof ViewGroup);
+ }
return root;
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 9ff29a8..4837ee5 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -26,11 +26,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
+import android.app.Activity;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -299,6 +301,11 @@
private VelocityTracker mVelocityTracker;
/**
+ * Determines strategy for velocity calculation
+ */
+ private @VelocityTracker.VelocityTrackerStrategy int mVelocityTrackerStrategy;
+
+ /**
* Consistency verifier for debugging purposes.
*/
private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
@@ -347,17 +354,17 @@
/**
* Creates a GestureDetector with the supplied listener.
- * This variant of the constructor should be used from a non-UI thread
+ * This variant of the constructor should be used from a non-UI thread
* (as it allows specifying the Handler).
- *
+ *
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler to use
*
* @throws NullPointerException if {@code listener} is null.
*
- * @deprecated Use {@link #GestureDetector(android.content.Context,
- * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
+ * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener, Handler)}
+ * instead.
*/
@Deprecated
public GestureDetector(@NonNull OnGestureListener listener, @Nullable Handler handler) {
@@ -367,15 +374,14 @@
/**
* Creates a GestureDetector with the supplied listener.
* You may only use this constructor from a UI thread (this is the usual situation).
- * @see android.os.Handler#Handler()
- *
+ * @see Handler#Handler()
+ *
* @param listener the listener invoked for all the callbacks, this must
* not be null.
- *
+ *
* @throws NullPointerException if {@code listener} is null.
*
- * @deprecated Use {@link #GestureDetector(android.content.Context,
- * android.view.GestureDetector.OnGestureListener)} instead.
+ * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener)} instead.
*/
@Deprecated
public GestureDetector(@NonNull OnGestureListener listener) {
@@ -384,10 +390,10 @@
/**
* Creates a GestureDetector with the supplied listener.
- * You may only use this constructor from a {@link android.os.Looper} thread.
- * @see android.os.Handler#Handler()
+ * You may only use this constructor from a {@link Looper} thread.
+ * @see Handler#Handler()
*
- * @param context An {@link android.app.Activity} or a {@link Context} created from
+ * @param context An {@link Activity} or a {@link Context} created from
* {@link Context#createWindowContext(int, Bundle)}
* @param listener the listener invoked for all the callbacks, this must
* not be null. If the listener implements the {@link OnDoubleTapListener} or
@@ -404,10 +410,10 @@
/**
* Creates a GestureDetector with the supplied listener that runs deferred events on the
- * thread associated with the supplied {@link android.os.Handler}.
- * @see android.os.Handler#Handler()
+ * thread associated with the supplied {@link Handler}.
+ * @see Handler#Handler()
*
- * @param context An {@link android.app.Activity} or a {@link Context} created from
+ * @param context An {@link Activity} or a {@link Context} created from
* {@link Context#createWindowContext(int, Bundle)}
* @param listener the listener invoked for all the callbacks, this must
* not be null. If the listener implements the {@link OnDoubleTapListener} or
@@ -419,6 +425,31 @@
*/
public GestureDetector(@Nullable @UiContext Context context,
@NonNull OnGestureListener listener, @Nullable Handler handler) {
+ this(context, listener, handler, VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener that runs deferred events on the
+ * thread associated with the supplied {@link Handler}.
+ * @see Handler#Handler()
+ *
+ * @param context An {@link Activity} or a {@link Context} created from
+ * {@link Context#createWindowContext(int, Bundle)}
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null. If the listener implements the {@link OnDoubleTapListener} or
+ * {@link OnContextClickListener} then it will also be set as the listener for
+ * these callbacks (for example when using the {@link SimpleOnGestureListener}).
+ * @param handler the handler to use for running deferred listener events.
+ * @param velocityTrackerStrategy strategy to use for velocity calculation of scroll/fling
+ * events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ *
+ * @hide
+ */
+ public GestureDetector(@Nullable @UiContext Context context,
+ @NonNull OnGestureListener listener, @Nullable Handler handler,
+ @VelocityTracker.VelocityTrackerStrategy int velocityTrackerStrategy) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
@@ -431,15 +462,16 @@
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
+ mVelocityTrackerStrategy = velocityTrackerStrategy;
init(context);
}
-
+
/**
* Creates a GestureDetector with the supplied listener that runs deferred events on the
- * thread associated with the supplied {@link android.os.Handler}.
- * @see android.os.Handler#Handler()
+ * thread associated with the supplied {@link Handler}.
+ * @see Handler#Handler()
*
- * @param context An {@link android.app.Activity} or a {@link Context} created from
+ * @param context An {@link Activity} or a {@link Context} created from
* {@link Context#createWindowContext(int, Bundle)}
* @param listener the listener invoked for all the callbacks, this must
* not be null.
@@ -547,7 +579,7 @@
mCurrentMotionEvent = MotionEvent.obtain(ev);
if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker = VelocityTracker.obtain(mVelocityTrackerStrategy);
}
mVelocityTracker.addMovement(ev);
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 9503f49..8c14de6 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -210,18 +210,9 @@
mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
notifyHideIme();
}
- if (mStartRootScrollY != 0) {
- // RootView is panned, ensure that it is scrolled back to the intended scroll position
- if (triggerBack) {
- // requesting ime as invisible
- mInsetsController.setRequestedVisibleTypes(0, ime());
- // changes the animation state and notifies RootView of changed insets, which
- // causes it to reset its scrollY to 0f (animated)
- mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
- } else {
- // This causes RootView to update its scroll back to the panned position
- mInsetsController.getHost().notifyInsetsChanged();
- }
+ if (mStartRootScrollY != 0 && !triggerBack) {
+ // This causes RootView to update its scroll back to the panned position
+ mInsetsController.getHost().notifyInsetsChanged();
}
}
@@ -237,6 +228,12 @@
// the IME away
mInsetsController.getHost().getInputMethodManager()
.notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken);
+
+ // requesting IME as invisible during post-commit
+ mInsetsController.setRequestedVisibleTypes(0, ime());
+ // Changes the animation state. This also notifies RootView of changed insets, which causes
+ // it to reset its scrollY to 0f (animated) if it was panned
+ mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
}
private void reset() {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index f1cb410..d7f2b01 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1262,10 +1262,13 @@
mHost.getInputMethodManager(), null /* icProto */);
}
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER,
+ ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+ mHost.isHandlingPointerEvent() /* fromUser */);
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType,
getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
- false /* useInsetsAnimationThread */, null /* statsToken */);
+ false /* useInsetsAnimationThread */, statsToken);
}
private void controlAnimationUnchecked(@InsetsType int types,
@@ -1567,7 +1570,9 @@
return;
}
final ImeTracker.Token statsToken = runner.getStatsToken();
- if (shown) {
+ if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+ ImeTracker.forLogging().onUserFinished(statsToken, shown);
+ } else if (shown) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
ImeTracker.forLogging().onShown(statsToken);
@@ -1838,6 +1843,9 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
mStartingAnimation = true;
+ if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+ ImeTracker.forLogging().onDispatched(runner.getStatsToken());
+ }
runner.setReadyDispatched(true);
listener.onReady(runner, types);
mStartingAnimation = false;
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index d31f823..27176a4 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -264,7 +264,6 @@
/**
* Obtains a velocity tracker with the specified strategy.
- * For testing and comparison purposes only.
*
* @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default.
* @return The velocity tracker.
@@ -272,6 +271,9 @@
* @hide
*/
public static VelocityTracker obtain(int strategy) {
+ if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
+ return obtain();
+ }
return new VelocityTracker(strategy);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 155c053..0715474 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -427,12 +427,6 @@
private static final long NANOS_PER_SEC = 1000000000;
- // If the ViewRootImpl has been idle for more than 200ms, clear the preferred
- // frame rate category and frame rate.
- private static final int IDLE_TIME_MILLIS = 250;
-
- private static final long NANOS_PER_MILLI = 1_000_000;
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -665,8 +659,6 @@
private int mMinusOneFrameIntervalMillis = 0;
// VRR interval between the previous and the frame before
private int mMinusTwoFrameIntervalMillis = 0;
- // VRR has the invalidation idle message been posted?
- private boolean mInvalidationIdleMessagePosted = false;
/**
* Update the Choreographer's FrameInfo object with the timing information for the current
@@ -929,15 +921,6 @@
private String mFrameRateCategoryView;
/**
- * The resolved pointer icon type requested by this window.
- * A null value indicates the resolved pointer icon has not yet been calculated.
- */
- // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
- @Nullable
- private Integer mPointerIconType = null;
- private PointerIcon mCustomPointerIcon = null;
-
- /**
* The resolved pointer icon requested by this window.
* A null value indicates the resolved pointer icon has not yet been calculated.
*/
@@ -4278,10 +4261,6 @@
// when the values are applicable.
if (mDrawnThisFrame) {
mDrawnThisFrame = false;
- if (!mInvalidationIdleMessagePosted) {
- mInvalidationIdleMessagePosted = true;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
- }
setCategoryFromCategoryCounts();
updateInfrequentCount();
setPreferredFrameRate(mPreferredFrameRate);
@@ -6442,7 +6421,6 @@
private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
- private static final int MSG_UPDATE_POINTER_ICON = 27;
private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
private static final int MSG_INSETS_CONTROL_CHANGED = 29;
private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30;
@@ -6507,8 +6485,6 @@
return "MSG_SYNTHESIZE_INPUT_EVENT";
case MSG_DISPATCH_WINDOW_SHOWN:
return "MSG_DISPATCH_WINDOW_SHOWN";
- case MSG_UPDATE_POINTER_ICON:
- return "MSG_UPDATE_POINTER_ICON";
case MSG_POINTER_CAPTURE_CHANGED:
return "MSG_POINTER_CAPTURE_CHANGED";
case MSG_INSETS_CONTROL_CHANGED:
@@ -6523,8 +6499,6 @@
return "MSG_WINDOW_TOUCH_MODE_CHANGED";
case MSG_KEEP_CLEAR_RECTS_CHANGED:
return "MSG_KEEP_CLEAR_RECTS_CHANGED";
- case MSG_CHECK_INVALIDATION_IDLE:
- return "MSG_CHECK_INVALIDATION_IDLE";
case MSG_REFRESH_POINTER_ICON:
return "MSG_REFRESH_POINTER_ICON";
case MSG_TOUCH_BOOST_TIMEOUT:
@@ -6761,10 +6735,6 @@
final int deviceId = msg.arg1;
handleRequestKeyboardShortcuts(receiver, deviceId);
} break;
- case MSG_UPDATE_POINTER_ICON: {
- MotionEvent event = (MotionEvent) msg.obj;
- resetPointerIcon(event);
- } break;
case MSG_POINTER_CAPTURE_CHANGED: {
final boolean hasCapture = msg.arg1 != 0;
handlePointerCaptureChanged(hasCapture);
@@ -6789,30 +6759,6 @@
mNumPausedForSync = 0;
scheduleTraversals();
break;
- case MSG_CHECK_INVALIDATION_IDLE: {
- long delta;
- if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) {
- delta = 0;
- } else {
- delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis;
- }
- if (delta >= IDLE_TIME_MILLIS) {
- mFrameRateCategoryHighCount = 0;
- mFrameRateCategoryHighHintCount = 0;
- mFrameRateCategoryNormalCount = 0;
- mFrameRateCategoryLowCount = 0;
- mPreferredFrameRate = 0;
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE);
- setPreferredFrameRate(0f);
- mInvalidationIdleMessagePosted = false;
- } else {
- mInvalidationIdleMessagePosted = true;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
- IDLE_TIME_MILLIS - delta);
- }
- break;
- }
case MSG_TOUCH_BOOST_TIMEOUT:
/**
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
@@ -7874,14 +7820,12 @@
|| action == MotionEvent.ACTION_HOVER_EXIT) {
// Other apps or the window manager may change the icon type outside of
// this app, therefore the icon type has to be reset on enter/exit event.
- mPointerIconType = null;
mResolvedPointerIcon = null;
}
if (action != MotionEvent.ACTION_HOVER_EXIT) {
// Resolve the pointer icon
if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
- mPointerIconType = null;
mResolvedPointerIcon = null;
}
}
@@ -7944,12 +7888,6 @@
return mAttachInfo.mHandlingPointerEvent;
}
- private void resetPointerIcon(MotionEvent event) {
- mPointerIconType = null;
- mResolvedPointerIcon = null;
- updatePointerIcon(event);
- }
-
/**
* If there is pointer that is showing a PointerIcon in this window, refresh the icon for that
@@ -8609,48 +8547,55 @@
private int mPendingKeyMetaState;
- private final GestureDetector mGestureDetector = new GestureDetector(mContext,
- new GestureDetector.OnGestureListener() {
- @Override
- public boolean onDown(@NonNull MotionEvent e) {
- // This can be ignored since it's not clear what KeyEvent this will
- // belong to.
- return true;
- }
-
- @Override
- public void onShowPress(@NonNull MotionEvent e) {
-
- }
-
- @Override
- public boolean onSingleTapUp(@NonNull MotionEvent e) {
- dispatchTap(e.getEventTime());
- return true;
- }
-
- @Override
- public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
- float distanceX, float distanceY) {
- // Scroll doesn't translate to DPAD events so should be ignored.
- return true;
- }
-
- @Override
- public void onLongPress(@NonNull MotionEvent e) {
- // Long presses don't translate to DPAD events so should be ignored.
- }
-
- @Override
- public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
- float velocityX, float velocityY) {
- dispatchFling(velocityX, velocityY, e2.getEventTime());
- return true;
- }
- });
+ private final GestureDetector mGestureDetector;
SyntheticTouchNavigationHandler() {
super(true);
+ int gestureDetectorVelocityStrategy =
+ android.companion.virtual.flags.Flags
+ .impulseVelocityStrategyForTouchNavigation()
+ ? VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE
+ : VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT;
+ mGestureDetector = new GestureDetector(mContext,
+ new GestureDetector.OnGestureListener() {
+ @Override
+ public boolean onDown(@NonNull MotionEvent e) {
+ // This can be ignored since it's not clear what KeyEvent this will
+ // belong to.
+ return true;
+ }
+
+ @Override
+ public void onShowPress(@NonNull MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(@NonNull MotionEvent e) {
+ dispatchTap(e.getEventTime());
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+ float distanceX, float distanceY) {
+ // Scroll doesn't translate to DPAD events so should be ignored.
+ return true;
+ }
+
+ @Override
+ public void onLongPress(@NonNull MotionEvent e) {
+ // Long presses don't translate to DPAD events so should be ignored.
+ }
+
+ @Override
+ public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+ float velocityX, float velocityY) {
+ dispatchFling(velocityX, velocityY, e2.getEventTime());
+ return true;
+ }
+ },
+ /* handler= */ null,
+ gestureDetectorVelocityStrategy);
}
public void process(MotionEvent event) {
@@ -13034,10 +12979,6 @@
private void removeVrrMessages() {
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
- if (mInvalidationIdleMessagePosted) {
- mInvalidationIdleMessagePosted = false;
- mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
- }
}
/**
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f454a6a..3091bf4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -754,6 +754,19 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */
+ static void onDispatched(@NonNull ImeTracker.Token statsToken) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onDispatched(statsToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
@AnyThread
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d992feb..edc9921 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -71,24 +71,40 @@
/** The type of the IME request. */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_SHOW,
- TYPE_HIDE
+ TYPE_HIDE,
+ TYPE_USER,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type {}
- /** IME show request type. */
+ /**
+ * IME show request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_SHOW
+ */
int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
- /** IME hide request type. */
+ /**
+ * IME hide request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_HIDE
+ */
int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
+ /**
+ * IME user-controlled animation request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_USER
+ */
+ int TYPE_USER = ImeProtoEnums.TYPE_USER;
+
/** The status of the IME request. */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_RUN,
STATUS_CANCEL,
STATUS_FAIL,
STATUS_SUCCESS,
- STATUS_TIMEOUT
+ STATUS_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface Status {}
@@ -117,7 +133,7 @@
@IntDef(prefix = { "ORIGIN_" }, value = {
ORIGIN_CLIENT,
ORIGIN_SERVER,
- ORIGIN_IME
+ ORIGIN_IME,
})
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
@@ -400,20 +416,36 @@
void onCancelled(@Nullable Token token, @Phase int phase);
/**
- * Called when the IME show request is successful.
+ * Called when the show IME request is successful.
*
* @param token the token tracking the current IME request or {@code null} otherwise.
*/
void onShown(@Nullable Token token);
/**
- * Called when the IME hide request is successful.
+ * Called when the hide IME request is successful.
*
* @param token the token tracking the current IME request or {@code null} otherwise.
*/
void onHidden(@Nullable Token token);
/**
+ * Called when the user-controlled IME request was dispatched to the requesting app. The
+ * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ */
+ void onDispatched(@Nullable Token token);
+
+ /**
+ * Called when the animation of the user-controlled IME request finished.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param shown whether the end state of the animation was shown or hidden.
+ */
+ void onUserFinished(@Nullable Token token, boolean shown);
+
+ /**
* Returns whether the current IME request was created due to a user interaction. This can
* only be {@code true} when running on the view's UI thread.
*
@@ -482,13 +514,6 @@
/** Whether the stack trace at the request call site should be logged. */
private boolean mLogStackTrace;
- private void reloadSystemProperties() {
- mLogProgress = SystemProperties.getBoolean(
- "persist.debug.imetracker", false);
- mLogStackTrace = SystemProperties.getBoolean(
- "persist.debug.imerequest.logstacktrace", false);
- }
-
@NonNull
@Override
public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@@ -497,7 +522,7 @@
final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
origin, reason, fromUser);
- Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+ Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type)
+ " at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
+ " fromUser " + fromUser,
@@ -552,6 +577,45 @@
Log.i(TAG, token.mTag + ": onHidden");
}
+
+ @Override
+ public void onDispatched(@Nullable Token token) {
+ if (token == null) return;
+ IInputMethodManagerGlobalInvoker.onDispatched(token);
+
+ Log.i(TAG, token.mTag + ": onDispatched");
+ }
+
+ @Override
+ public void onUserFinished(@Nullable Token token, boolean shown) {
+ if (token == null) return;
+ // This is already sent to ImeTrackerService to mark it finished during onDispatched.
+
+ Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden"));
+ }
+
+ /**
+ * Gets the prefix string for {@link #onStart} based on the given request type.
+ *
+ * @param type request type for which to create the prefix string with.
+ */
+ @NonNull
+ private static String getOnStartPrefix(@Type int type) {
+ return switch (type) {
+ case TYPE_SHOW -> "onRequestShow";
+ case TYPE_HIDE -> "onRequestHide";
+ case TYPE_USER -> "onRequestUser";
+ default -> "onRequestUnknown";
+ };
+ }
+
+ /** Reloads the system properties related to this class. */
+ private void reloadSystemProperties() {
+ mLogProgress = SystemProperties.getBoolean(
+ "persist.debug.imetracker", false);
+ mLogStackTrace = SystemProperties.getBoolean(
+ "persist.debug.imerequest.logstacktrace", false);
+ }
};
/** The singleton IME tracker instance for instrumenting jank metrics. */
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7885cd9..11ee286 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -54,6 +54,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -431,6 +432,14 @@
* @hide
*/
public InputMethodInfo(InputMethodInfo source) {
+ this(source, Collections.emptyList());
+ }
+
+ /**
+ * @hide
+ */
+ public InputMethodInfo(@NonNull InputMethodInfo source,
+ @NonNull List<InputMethodSubtype> additionalSubtypes) {
mId = source.mId;
mSettingsActivityName = source.mSettingsActivityName;
mLanguageSettingsActivityName = source.mLanguageSettingsActivityName;
@@ -445,7 +454,19 @@
mIsVrOnly = source.mIsVrOnly;
mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly;
mService = source.mService;
- mSubtypes = source.mSubtypes;
+ if (additionalSubtypes.isEmpty()) {
+ mSubtypes = source.mSubtypes;
+ } else {
+ final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList();
+ final int additionalSubtypeCount = additionalSubtypes.size();
+ for (int i = 0; i < additionalSubtypeCount; ++i) {
+ final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i);
+ if (!subtypes.contains(additionalSubtype)) {
+ subtypes.add(additionalSubtype);
+ }
+ }
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
+ }
mHandledConfigChanges = source.mHandledConfigChanges;
mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting;
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index c243a22..e2d343f 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -25,6 +25,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -163,6 +164,18 @@
}
/**
+ * @return A list of {@link InputMethodInfo} copied from this array.
+ */
+ @NonNull
+ public ArrayList<InputMethodSubtype> toList() {
+ final ArrayList<InputMethodSubtype> list = new ArrayList<>(mCount);
+ for (int i = 0; i < mCount; ++i) {
+ list.add(get(i));
+ }
+ return list;
+ }
+
+ /**
* Return the number of {@link InputMethodSubtype} objects.
*/
public int getCount() {
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index 3a39631..503e542 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -47,3 +47,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "messaging_child_request_layout"
+ namespace: "systemui"
+ description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure."
+ bug: "324537506"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 31e3a34..af3d7eb 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -56,6 +56,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.DecorView;
+import java.io.Closeable;
+import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
@@ -568,6 +570,12 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
releaseAnimationSurfaceHost();
+ if (mIconView instanceof ImageView imageView
+ && imageView.getDrawable() instanceof Closeable closeableDrawable) {
+ try {
+ closeableDrawable.close();
+ } catch (IOException ignore) { }
+ }
}
@Override
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index d0f3c3e..6f7660a 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -84,8 +84,8 @@
/**
* Sets the activity navigation to be isolated, where the activity navigation on the
* TaskFragment is separated from the rest activities in the Task. Activities cannot be
- * started on an isolated TaskFragment unless the activities are launched from the same
- * TaskFragment or explicitly requested to.
+ * started on an isolated TaskFragment unless explicitly requested to. That said, new launched
+ * activities should be positioned as a sibling to the TaskFragment with higher z-ordering.
*/
public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
@@ -149,6 +149,18 @@
*/
public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+ /**
+ * Sets the TaskFragment to be pinned.
+ * <p>
+ * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other
+ * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the
+ * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or
+ * explicitly requested to. Non-embeddable activities are not restricted to.
+ * <p>
+ * See {@link #OP_TYPE_REORDER_TO_FRONT} on how to reorder a pinned TaskFragment to the top.
+ */
+ public static final int OP_TYPE_SET_PINNED = 19;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -170,6 +182,7 @@
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
+ OP_TYPE_SET_PINNED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index a77c234..1555416 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.os.Parcel;
@@ -27,10 +29,13 @@
import java.util.Objects;
/**
- * The information about the parent Task of a particular TaskFragment
+ * The information about the parent Task of a particular TaskFragment.
+ *
* @hide
*/
-public class TaskFragmentParentInfo implements Parcelable {
+@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+@TestApi
+public final class TaskFragmentParentInfo implements Parcelable {
@NonNull
private final Configuration mConfiguration = new Configuration();
@@ -42,6 +47,7 @@
@Nullable private final SurfaceControl mDecorSurface;
+ /** @hide */
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
mConfiguration.setTo(configuration);
@@ -51,6 +57,7 @@
mDecorSurface = decorSurface;
}
+ /** @hide */
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
@@ -59,7 +66,11 @@
mDecorSurface = info.mDecorSurface;
}
- /** The {@link Configuration} of the parent Task */
+ /**
+ * The {@link Configuration} of the parent Task
+ *
+ * @hide
+ */
@NonNull
public Configuration getConfiguration() {
return mConfiguration;
@@ -68,19 +79,27 @@
/**
* The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
* Task is detached from previously associated display.
+ *
+ * @hide
*/
public int getDisplayId() {
return mDisplayId;
}
- /** Whether the parent Task is visible or not */
+ /**
+ * Whether the parent Task is visible or not
+ *
+ * @hide
+ */
public boolean isVisible() {
return mVisible;
}
/**
* Whether the parent Task has any direct child activity, which is not embedded in any
- * TaskFragment, or not
+ * TaskFragment, or not.
+ *
+ * @hide
*/
public boolean hasDirectActivity() {
return mHasDirectActivity;
@@ -93,6 +112,8 @@
* {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
* Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
* be dispatched to the client.
+ *
+ * @hide
*/
public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
if (that == null) {
@@ -103,6 +124,7 @@
&& mDecorSurface == that.mDecorSurface;
}
+ /** @hide */
@Nullable
public SurfaceControl getDecorSurface() {
return mDecorSurface;
@@ -156,6 +178,7 @@
return result;
}
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
@@ -173,6 +196,8 @@
mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
}
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+ @NonNull
public static final Creator<TaskFragmentParentInfo> CREATOR =
new Creator<TaskFragmentParentInfo>() {
@Override
@@ -186,6 +211,7 @@
}
};
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 4dada10..32e3f5a 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -24,7 +24,6 @@
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Intent;
-import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -248,13 +247,6 @@
return this;
}
- // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
- /** Configuration of the parent Task. */
- @NonNull
- public Change setTaskConfiguration(@NonNull Configuration configuration) {
- return this;
- }
-
/**
* If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
* from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
@@ -299,12 +291,11 @@
return this;
}
- // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
/**
* Sets info of the parent Task of the embedded TaskFragment.
* @see TaskFragmentParentInfo
*
- * @hide pending unhide
+ * @hide
*/
@NonNull
public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
@@ -338,12 +329,6 @@
return mTaskId;
}
- // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
- @Nullable
- public Configuration getTaskConfiguration() {
- return mTaskFragmentParentInfo.getConfiguration();
- }
-
@Nullable
public IBinder getErrorCallbackToken() {
return mErrorCallbackToken;
@@ -365,8 +350,10 @@
return mActivityToken;
}
- // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
- /** @hide pending unhide */
+ /**
+ * Obtains the {@link TaskFragmentParentInfo} for this transaction.
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Nullable
public TaskFragmentParentInfo getTaskFragmentParentInfo() {
return mTaskFragmentParentInfo;
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index 41b6d31..a2e3d40 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -16,6 +16,7 @@
package android.window;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -32,6 +33,9 @@
import android.view.Surface;
import android.view.WindowInsetsController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Represents a task snapshot.
* @hide
@@ -68,6 +72,21 @@
private final boolean mHasImeSurface;
// Must be one of the named color spaces, otherwise, always use SRGB color space.
private final ColorSpace mColorSpace;
+ private int mInternalReferences;
+
+ /** This snapshot object is being broadcast. */
+ public static final int REFERENCE_BROADCAST = 1;
+ /** This snapshot object is in the cache. */
+ public static final int REFERENCE_CACHE = 1 << 1;
+ /** This snapshot object is being persistent. */
+ public static final int REFERENCE_PERSIST = 1 << 2;
+ @IntDef(flag = true, prefix = { "REFERENCE_" }, value = {
+ REFERENCE_BROADCAST,
+ REFERENCE_CACHE,
+ REFERENCE_PERSIST
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ReferenceFlags {}
public TaskSnapshot(long id, long captureTime,
@NonNull ComponentName topActivityComponent, HardwareBuffer snapshot,
@@ -296,7 +315,28 @@
+ " mWindowingMode=" + mWindowingMode
+ " mAppearance=" + mAppearance
+ " mIsTranslucent=" + mIsTranslucent
- + " mHasImeSurface=" + mHasImeSurface;
+ + " mHasImeSurface=" + mHasImeSurface
+ + " mInternalReferences=" + mInternalReferences;
+ }
+
+ /**
+ * Adds a reference when the object is held somewhere.
+ * Only used in core.
+ */
+ public synchronized void addReference(@ReferenceFlags int usage) {
+ mInternalReferences |= usage;
+ }
+
+ /**
+ * Removes a reference when the object is not held from somewhere. The snapshot will be closed
+ * once the reference becomes zero.
+ * Only used in core.
+ */
+ public synchronized void removeReference(@ReferenceFlags int usage) {
+ mInternalReferences &= ~usage;
+ if (mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) {
+ mSnapshot.close();
+ }
}
public static final @NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 64fe66e..ec4e3e9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.ComponentName;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.WindowManager;
@@ -180,6 +181,7 @@
public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
public ComponentName mTopActivity;
+ public IBinder mLaunchCookie;
public Requirement() {
}
@@ -193,6 +195,7 @@
mMustBeTask = in.readBoolean();
mOrder = in.readInt();
mTopActivity = in.readTypedObject(ComponentName.CREATOR);
+ mLaunchCookie = in.readStrongBinder();
}
/** Go through changes and find if at-least one change matches this filter */
@@ -231,6 +234,9 @@
if (mMustBeTask && change.getTaskInfo() == null) {
continue;
}
+ if (!matchesCookie(change.getTaskInfo())) {
+ continue;
+ }
return true;
}
return false;
@@ -247,13 +253,25 @@
return false;
}
+ private boolean matchesCookie(ActivityManager.RunningTaskInfo info) {
+ if (mLaunchCookie == null) return true;
+ if (info == null) return false;
+ for (IBinder cookie : info.launchCookies) {
+ if (mLaunchCookie.equals(cookie)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Check if the request matches this filter. It may generate false positives */
boolean matches(@NonNull TransitionRequestInfo request) {
// Can't check modes/order since the transition hasn't been built at this point.
if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
return request.getTriggerTask() != null
&& request.getTriggerTask().getActivityType() == mActivityType
- && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */);
+ && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */)
+ && matchesCookie(request.getTriggerTask());
}
@Override
@@ -267,6 +285,7 @@
dest.writeBoolean(mMustBeTask);
dest.writeInt(mOrder);
dest.writeTypedObject(mTopActivity, flags);
+ dest.writeStrongBinder(mLaunchCookie);
}
@NonNull
@@ -307,6 +326,7 @@
out.append(" mustBeTask=" + mMustBeTask);
out.append(" order=" + containerOrderToString(mOrder));
out.append(" topActivity=").append(mTopActivity);
+ out.append(" launchCookie=").append(mLaunchCookie);
out.append("}");
return out.toString();
}
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 cd13c4a..4b2beb9 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
@@ -9,6 +9,16 @@
}
flag {
+ name: "disable_thin_letterboxing_policy"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether reachability is disabled in case of thin letterboxing"
+ bug: "341027847"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+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/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 6864bf7..8e18f84 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -24,6 +24,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED;
@@ -32,6 +33,7 @@
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON_LONG_PRESS;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
@@ -48,7 +50,6 @@
import android.content.Context;
import android.provider.Settings;
-import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.util.FrameworkStatsLog;
@@ -248,6 +249,8 @@
}
case HARDWARE:
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+ case QUICK_SETTINGS:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS;
}
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
}
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index faaf7d5..8132652 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -24,6 +24,8 @@
import android.content.pm.PackagePartitions.SystemPartition;
import android.os.Build;
import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Xml;
@@ -241,6 +243,18 @@
}
}
+ @FunctionalInterface
+ public interface SysPropWrapper{
+ /**
+ * Get system property
+ *
+ * @param property the key to look up.
+ *
+ * @return The property value if found, empty string otherwise.
+ */
+ String get(String property);
+ }
+
/**
* Retrieves overlays configured within the partition in increasing priority order.
*
@@ -320,6 +334,76 @@
}
/**
+ * Expand the property inside a rro configuration path.
+ *
+ * A RRO configuration can contain a property, this method expands
+ * the property to its value.
+ *
+ * Only read only properties allowed, prefixed with ro. Other
+ * properties will raise exception.
+ *
+ * Only a single property in the path is allowed.
+ *
+ * Example "${ro.boot.hardware.sku}/config.xml" would expand to
+ * "G020N/config.xml"
+ *
+ * @param configPath path to expand
+ * @param sysPropWrapper method used for reading properties
+ *
+ * @return The expanded path. Returns null if configPath is null.
+ */
+ @VisibleForTesting
+ public static String expandProperty(String configPath,
+ SysPropWrapper sysPropWrapper) {
+ if (configPath == null) {
+ return null;
+ }
+
+ int propStartPos = configPath.indexOf("${");
+ if (propStartPos == -1) {
+ // No properties inside the string, return as is
+ return configPath;
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(configPath.substring(0, propStartPos));
+
+ // Read out the end position
+ int propEndPos = configPath.indexOf("}", propStartPos);
+ if (propEndPos == -1) {
+ throw new IllegalStateException("Malformed property, unmatched braces, in: "
+ + configPath);
+ }
+
+ // Confirm that there is only one property inside the string
+ if (configPath.indexOf("${", propStartPos + 2) != -1) {
+ throw new IllegalStateException("Only a single property supported in path: "
+ + configPath);
+ }
+
+ final String propertyName = configPath.substring(propStartPos + 2, propEndPos);
+ if (!propertyName.startsWith("ro.")) {
+ throw new IllegalStateException("Only read only properties can be used when "
+ + "merging RRO config files: " + propertyName);
+ }
+ final String propertyValue = sysPropWrapper.get(propertyName);
+ if (TextUtils.isEmpty(propertyValue)) {
+ throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName);
+ }
+ Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName));
+ sb.append(propertyValue);
+
+ // propEndPos points to '}', need to step to next character, might be outside of string
+ propEndPos = propEndPos + 1;
+ // Append the remainder, if exists
+ if (propEndPos < configPath.length()) {
+ sb.append(configPath.substring(propEndPos));
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Parses a <merge> tag within an overlay configuration file.
*
* Merge tags allow for other configuration files to be "merged" at the current parsing
@@ -331,10 +415,21 @@
@Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
- final String path = parser.getAttributeValue(null, "path");
+ final String path;
+
+ try {
+ SysPropWrapper sysPropWrapper = p -> {
+ return SystemProperties.get(p, "");
+ };
+ path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper);
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException(String.format("<merge> path expand error in %s at %s",
+ configFile, parser.getPositionDescription()), e);
+ }
+
if (path == null) {
- throw new IllegalStateException(String.format("<merge> without path in %s at %s"
- + configFile, parser.getPositionDescription()));
+ throw new IllegalStateException(String.format("<merge> without path in %s at %s",
+ configFile, parser.getPositionDescription()));
}
if (path.startsWith("/")) {
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index ab4edb6..ebae39e 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -64,20 +64,28 @@
oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
/**
- * Called when the IME show request is successful.
+ * Called when the show IME request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onShown(in ImeTracker.Token statsToken);
/**
- * Called when the IME hide request is successful.
+ * Called when the hide IME request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onHidden(in ImeTracker.Token statsToken);
/**
+ * Called when the user-controlled IME request was dispatched to the requesting app. The
+ * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+ *
+ * @param statsToken the token tracking the current IME request.
+ */
+ oneway void onDispatched(in ImeTracker.Token statsToken);
+
+ /**
* Checks whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
index 91b80dd..24cd1c9 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
@@ -52,8 +52,14 @@
ImeTracingPerfettoImpl() {
Producer.init(InitArguments.DEFAULTS);
- mDataSource.register(
- new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .setNoFlush(true)
+ .setWillNotifyOnStop(false)
+ .build();
+ mDataSource.register(params);
}
diff --git a/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java
new file mode 100644
index 0000000..92d453b
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java
@@ -0,0 +1,81 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.annotation.BinderThread;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+/**
+ * An internal interface that mirrors {@link IInlineSuggestionsRequestCallback}.
+ *
+ * <p>This interface is used to forward incoming IPCs from
+ * {@link com.android.server.inputmethod.AutofillSuggestionsController} to
+ * {@link com.android.server.autofill.AutofillInlineSuggestionsRequestSession}.</p>
+ */
+public interface InlineSuggestionsRequestCallback {
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}.
+ */
+ @BinderThread
+ void onInlineSuggestionsUnsupported();
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest(
+ * InlineSuggestionsRequest, IInlineSuggestionsResponseCallback)}.
+ */
+ @BinderThread
+ void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback);
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInput(AutofillId)}.
+ */
+ @BinderThread
+ void onInputMethodStartInput(AutofillId imeFieldId);
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodShowInputRequested(boolean)}.
+ */
+ @BinderThread
+ void onInputMethodShowInputRequested(boolean requestResult);
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView()}.
+ */
+ @BinderThread
+ void onInputMethodStartInputView();
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInputView()}.
+ */
+ @BinderThread
+ void onInputMethodFinishInputView();
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInput()}.
+ */
+ @BinderThread
+ void onInputMethodFinishInput();
+
+ /**
+ * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsSessionInvalidated()}.
+ */
+ @BinderThread
+ void onInlineSuggestionsSessionInvalidated();
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a0aad31..2a5593f 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -297,6 +297,8 @@
return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION:
return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
+ case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION:
+ return "CONTROL_WINDOW_INSETS_ANIMATION";
default:
return "Unknown=" + reason;
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index da738a0..eb6a810 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -88,6 +88,7 @@
SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
+ SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
})
public @interface SoftInputShowHideReason {
/** Default, undefined reason. */
@@ -397,4 +398,10 @@
* {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}.
*/
int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION;
+
+ /**
+ * Show / Hide soft input by application-controlled animation in
+ * {@link android.view.InsetsController#controlWindowInsetsAnimation}.
+ */
+ int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 9f9aae5..24971f5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
@@ -34,8 +35,12 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
@@ -75,6 +80,10 @@
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class Descriptor {
+ public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
+ public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
+ public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
+
public static final String XML_TAG_DESCRIPTOR = "descriptor";
private static final String XML_ATTR_ID = "id";
private static final String XML_ATTR_NAME = "name";
@@ -120,6 +129,10 @@
*/
public final PersistableBundle extras;
+ private PowerStatsFormatter mDeviceStatsFormatter;
+ private PowerStatsFormatter mStateStatsFormatter;
+ private PowerStatsFormatter mUidStatsFormatter;
+
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
int statsArrayLength, @Nullable SparseArray<String> stateLabels,
int stateStatsArrayLength, int uidStatsArrayLength,
@@ -131,7 +144,7 @@
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
@Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
- int uidStatsArrayLength, PersistableBundle extras) {
+ int uidStatsArrayLength, @NonNull PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -154,6 +167,39 @@
}
/**
+ * Returns a custom formatter for this type of power stats.
+ */
+ public PowerStatsFormatter getDeviceStatsFormatter() {
+ if (mDeviceStatsFormatter == null) {
+ mDeviceStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_DEVICE_STATS_FORMAT));
+ }
+ return mDeviceStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-state stats.
+ */
+ public PowerStatsFormatter getStateStatsFormatter() {
+ if (mStateStatsFormatter == null) {
+ mStateStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_STATE_STATS_FORMAT));
+ }
+ return mStateStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-UID stats.
+ */
+ public PowerStatsFormatter getUidStatsFormatter() {
+ if (mUidStatsFormatter == null) {
+ mUidStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_UID_STATS_FORMAT));
+ }
+ return mUidStatsFormatter;
+ }
+
+ /**
* Returns the label associated with the give state key, e.g. "5G-high" for the
* state of Mobile Radio representing the 5G mode and high signal power.
*/
@@ -491,20 +537,22 @@
StringBuilder sb = new StringBuilder();
sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
if (stats.length > 0) {
- sb.append("=").append(Arrays.toString(stats));
+ sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
- sb.append(" [");
+ sb.append(" (");
sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
- sb.append("]=");
- sb.append(Arrays.toString(stateStats.valueAt(i)));
+ sb.append(") ");
+ sb.append(formatter.format(stateStats.valueAt(i)));
}
}
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
sb.append(uidPrefix)
.append(UserHandle.formatUid(uidStats.keyAt(i)))
- .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
+ .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
}
return sb.toString();
}
@@ -513,26 +561,29 @@
* Prints the contents of the stats snapshot.
*/
public void dump(IndentingPrintWriter pw) {
- pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
+ pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
+
if (descriptor.statsArrayLength != 0) {
- pw.print("stats", Arrays.toString(stats)).println();
+ pw.println(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
- pw.print("state ");
+ pw.print(" (");
pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
- pw.print(": ");
- pw.print(Arrays.toString(stateStats.valueAt(i)));
+ pw.print(") ");
+ pw.print(formatter.format(stateStats.valueAt(i)));
pw.println();
}
}
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
pw.print("UID ");
- pw.print(uidStats.keyAt(i));
+ pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
pw.print(": ");
- pw.print(Arrays.toString(uidStats.valueAt(i)));
+ pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
pw.println();
}
pw.decreaseIndent();
@@ -542,4 +593,126 @@
public String toString() {
return "PowerStats: " + formatForBatteryHistory(" UID ");
}
+
+ public static class PowerStatsFormatter {
+ private static class Section {
+ public String label;
+ public int position;
+ public int length;
+ public boolean optional;
+ public boolean typePower;
+ }
+
+ private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
+ private static final Pattern SECTION_PATTERN =
+ Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
+ private final List<Section> mSections;
+
+ public PowerStatsFormatter(String format) {
+ mSections = parseFormat(format);
+ }
+
+ /**
+ * Produces a formatted string representing the supplied array, with labels
+ * and other adornments specific to the power stats layout.
+ */
+ public String format(long[] stats) {
+ return format(mSections, stats);
+ }
+
+ private List<Section> parseFormat(String format) {
+ if (format == null || format.isBlank()) {
+ return null;
+ }
+
+ ArrayList<Section> sections = new ArrayList<>();
+ Matcher matcher = SECTION_PATTERN.matcher(format);
+ for (int position = 0; position < format.length(); position = matcher.end()) {
+ if (!matcher.find() || matcher.start() != position) {
+ Slog.wtf(TAG, "Bad power stats format '" + format + "'");
+ return null;
+ }
+ Section section = new Section();
+ section.label = matcher.group(1);
+ section.position = Integer.parseUnsignedInt(matcher.group(2));
+ String length = matcher.group("L");
+ if (length != null) {
+ section.length = Integer.parseUnsignedInt(length);
+ } else {
+ section.length = 1;
+ }
+ String flags = matcher.group("F");
+ if (flags != null) {
+ for (int i = 0; i < flags.length(); i++) {
+ char flag = flags.charAt(i);
+ switch (flag) {
+ case '?':
+ section.optional = true;
+ break;
+ case 'p':
+ section.typePower = true;
+ break;
+ default:
+ Slog.e(TAG,
+ "Unsupported format option '" + flag + "' in " + format);
+ break;
+ }
+ }
+ }
+ sections.add(section);
+ }
+
+ return sections;
+ }
+
+ private String format(List<Section> sections, long[] stats) {
+ if (sections == null) {
+ return Arrays.toString(stats);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, count = sections.size(); i < count; i++) {
+ Section section = sections.get(i);
+ if (section.length == 0) {
+ continue;
+ }
+
+ if (section.optional) {
+ boolean nonZero = false;
+ for (int offset = 0; offset < section.length; offset++) {
+ if (stats[section.position + offset] != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ continue;
+ }
+ }
+
+ if (!sb.isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(section.label).append(": ");
+ if (section.length != 1) {
+ sb.append('[');
+ }
+ for (int offset = 0; offset < section.length; offset++) {
+ if (offset != 0) {
+ sb.append(", ");
+ }
+ if (section.typePower) {
+ sb.append(BatteryStats.formatCharge(
+ stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
+ } else {
+ sb.append(stats[section.position + offset]);
+ }
+ }
+ if (section.length != 1) {
+ sb.append(']');
+ }
+ }
+ return sb.toString();
+ }
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index ee33eb4..37b7288 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -131,8 +131,13 @@
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
- mDataSource.register(new DataSourceParams(
- DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ DataSourceParams
+ .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .build();
+ mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
this.mLogGroups = logGroups;
@@ -186,8 +191,6 @@
}
os.end(outProtologViewerConfigToken);
-
- ctx.flush();
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
}
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
new file mode 100644
index 0000000..1340156
--- /dev/null
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.ravenwood;
+
+/**
+ * Class to interact with the Ravenwood environment.
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class RavenwoodEnvironment {
+ private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
+
+ private RavenwoodEnvironment() {
+ }
+
+ /**
+ * @return the singleton instance.
+ */
+ public static RavenwoodEnvironment getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
+ *
+ * <p>Using this allows code to behave differently on a real device and on Ravenwood, but
+ * generally speaking, that's a bad idea because we want the test target code to behave
+ * differently.
+ *
+ * <p>This should be only used when different behavior is absolutely needed.
+ *
+ * <p>If someone needs it without having access to the SDK, the following hack would work too.
+ * <code>System.getProperty("java.class.path").contains("ravenwood")</code>
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ public boolean isRunningOnRavenwood() {
+ return false;
+ }
+
+ public boolean isRunningOnRavenwood$ravenwood() {
+ return true;
+ }
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f931a76..e29f256 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -47,7 +47,7 @@
void animateExpandNotificationsPanel();
void animateExpandSettingsPanel(String subPanel);
void animateCollapsePanels();
- void togglePanel();
+ void toggleNotificationsPanel();
void showWirelessChargingAnimation(int batteryLevel);
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index e07acac..3d8237e 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import static android.widget.flags.Flags.messagingChildRequestLayout;
+
import android.annotation.Nullable;
import android.annotation.Px;
import android.content.Context;
@@ -92,6 +94,10 @@
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.hide = true;
+ // Child always needs to be measured to calculate hide property correctly in onMeasure.
+ if (messagingChildRequestLayout()) {
+ child.requestLayout();
+ }
if (child instanceof MessagingChild) {
MessagingChild messagingChild = (MessagingChild) child;
// Whenever we encounter the message first, it's always first in the layout
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index 4031b2f..0f4615a 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -16,18 +16,27 @@
package com.android.internal.widget;
+import android.annotation.Nullable;
import android.app.Flags;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
import android.widget.RemoteViews;
-import androidx.annotation.Nullable;
-
/**
* An image view that holds the icon displayed on the left side of a notification row.
*/
@RemoteViews.RemoteView
public class NotificationRowIconView extends CachingIconView {
+ private boolean mApplyCircularCrop = false;
+
public NotificationRowIconView(Context context) {
super(context);
}
@@ -57,4 +66,82 @@
super.onFinishInflate();
}
+
+ @Nullable
+ @Override
+ Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+ final Drawable original = super.loadSizeRestrictedIcon(icon);
+ final Drawable result;
+ if (mApplyCircularCrop) {
+ result = makeCircularDrawable(original);
+ } else {
+ result = original;
+ }
+
+ return result;
+ }
+
+ /**
+ * Enables circle crop that makes given image circular
+ */
+ @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync")
+ public void setApplyCircularCrop(boolean applyCircularCrop) {
+ mApplyCircularCrop = applyCircularCrop;
+ }
+
+ /**
+ * Async version of {@link NotificationRowIconView#setApplyCircularCrop}
+ */
+ public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) {
+ mApplyCircularCrop = applyCircularCrop;
+ return () -> {
+ };
+ }
+
+ @Nullable
+ private Drawable makeCircularDrawable(@Nullable Drawable original) {
+ if (original == null) {
+ return original;
+ }
+
+ final Bitmap source = drawableToBitmap(original);
+
+ int size = Math.min(source.getWidth(), source.getHeight());
+
+ Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false);
+ Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+ final Canvas canvas = new Canvas(result);
+ final Paint paint = new Paint();
+ paint.setShader(
+ new BitmapShader(squared, BitmapShader.TileMode.CLAMP,
+ BitmapShader.TileMode.CLAMP));
+ paint.setAntiAlias(true);
+ float radius = size / 2f;
+ canvas.drawCircle(radius, radius, radius, paint);
+ return new BitmapDrawable(getResources(), result);
+ }
+
+ private static Bitmap drawableToBitmap(Drawable drawable) {
+ if (drawable instanceof BitmapDrawable bitmapDrawable) {
+ final Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+ return bitmap.copy(Bitmap.Config.ARGB_8888, false);
+ } else {
+ return bitmap;
+ }
+ }
+
+ int width = drawable.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = drawable.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
}
diff --git a/core/java/com/android/internal/widget/TEST_MAPPING b/core/java/com/android/internal/widget/TEST_MAPPING
new file mode 100644
index 0000000..91cecfd
--- /dev/null
+++ b/core/java/com/android/internal/widget/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test
+ "sysui-screenshot-test": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index f82ebfe..17129d8 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -244,8 +244,8 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
}
-void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
- ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr);
+void nativeWritePackets(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeWritePackets(%p)", (void*)ds_ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr);
datasource->WritePackets(env, packets);
}
@@ -256,10 +256,12 @@
}
void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
- jint buffer_exhausted_policy) {
+ jint buffer_exhausted_policy, jboolean will_notify_on_stop,
+ jboolean no_flush) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
struct PerfettoDsParams params = PerfettoDsParamsDefault();
+ params.will_notify_on_stop = will_notify_on_stop;
params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
params.user_arg = reinterpret_cast<void*>(datasource.get());
@@ -325,13 +327,15 @@
datasource_instance->onStart(env);
};
- params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
- struct PerfettoDsOnFlushArgs*) {
- JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+ if (!no_flush) {
+ params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*,
+ void* inst_ctx, struct PerfettoDsOnFlushArgs*) {
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
- auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
- datasource_instance->onFlush(env);
- };
+ auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+ datasource_instance->onFlush(env);
+ };
+ }
params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
void* inst_ctx, struct PerfettoDsOnStopArgs*) {
@@ -422,7 +426,7 @@
(void*)nativeCreate},
{"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
{"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
- {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+ {"nativeRegisterDataSource", "(JIZZ)V", (void*)nativeRegisterDataSource},
{"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
(void*)nativeGetPerfettoInstanceLocked},
{"nativeReleasePerfettoInstanceLocked", "(JI)V",
@@ -431,11 +435,12 @@
{"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin},
{"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext},
{"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak},
- {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}};
+ {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex},
+
+ {"nativeWritePackets", "(J[[B)V", (void*)nativeWritePackets}};
const JNINativeMethod gMethodsTracingContext[] = {
/* name, signature, funcPtr */
- {"nativeFlush", "(J[[B)V", (void*)nativeFlush},
{"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls},
{"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState},
{"nativeSetCustomTls", "(JLjava/lang/Object;)V", (void*)nativeSetCustomTls},
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index bf2fdda..acef609 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -330,7 +330,7 @@
InputDeviceInfo info = InputDeviceInfo();
info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
"keyboard " + std::to_string(keyboardId), true, false,
- ui::ADISPLAY_ID_DEFAULT);
+ ui::LogicalDisplayId::DEFAULT);
info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
info.setKeyCharacterMap(*charMap);
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index c92435f..654d83c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -607,7 +607,7 @@
optional InsetsSourceProviderProto insets_source_provider = 1;
optional WindowStateProto ime_target_from_ime = 2;
- optional bool is_ime_layout_drawn = 3;
+ optional bool is_ime_layout_drawn = 3 [deprecated=true];
}
message BackNavigationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 70d923b..8541704 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -299,6 +299,9 @@
<protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.hardware.hdmi.action.OSD_MESSAGE" />
+ <protected-broadcast android:name="android.hardware.hdmi.action.ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI" />
+
<protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
diff --git a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
index 7c45c20..c692967 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 1 bar. move to higher ground. -->
<path
android:name="ic_signal_cellular_1_4_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H11 V20 H6 z" />
+ android:pathData="M0,0 H11 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
index 02b646d..b01c269 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 1 bar. might have to call you back. -->
<path
android:name="ic_signal_cellular_1_5_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H12 V20 H6 z" />
+ android:pathData="M0,0 H12 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
index 514d169..982623d 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 2 bars. 2 out of 4 ain't bad. -->
<path
android:name="ic_signal_cellular_2_4_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H14 V20 H6 z" />
+ android:pathData="M0,0 H14 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
index a97f771..75daadd 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
@@ -23,11 +23,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 2 bars. hanging in there. -->
<path
android:name="ic_signal_cellular_2_5_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H14 V20 H6 z" />
+ android:pathData="M0,0 H14 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
index 1bacf4a..4e4bea3 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 3 bars. quite nice. -->
<path
android:name="ic_signal_cellular_3_4_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H17 V20 H6 z" />
+ android:pathData="M0,0 H17 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
index 2789d3e..9a98c29 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 3 bars. not great, not terrible. -->
<path
android:name="ic_signal_cellular_3_5_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H16 V20 H6 z" />
+ android:pathData="M0,0 H16 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
index 8286dbb..2a37d01 100644
--- a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
@@ -22,11 +22,11 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
- <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z">
+ <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
<!-- 4 bars. extremely respectable. -->
<path
android:name="ic_signal_cellular_4_5_bar"
android:fillColor="@android:color/white"
- android:pathData="M6,0 H18 V20 H6 z" />
+ android:pathData="M0,0 H18 V24 H0 z" />
</clip-path>
</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
new file mode 100644
index 0000000..3b288d7
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ android:tag="compactMessagingHUN"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no">
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ />
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
+ >
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+ <include layout="@layout/notification_top_line_views" />
+ </NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/reply_action_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_action_list_height"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" />
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+ </FrameLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 2c35c9b..78299ab 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -25,6 +25,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="6"
+ android:paddingBottom="10dp"
android:text="@string/fp_power_button_enrollment_title"
android:textColor="@color/side_fps_text_color"
android:paddingLeft="20dp"/>
@@ -36,6 +37,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="3"
+ android:paddingBottom="10dp"
android:text="@string/fp_power_button_enrollment_button_text"
style="?android:attr/buttonBarNegativeButtonStyle"
android:textColor="@color/side_fps_button_color"
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 31acd9a..5266214 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -93,4 +93,7 @@
<!-- If this is true, allow wake from theater mode from motion. -->
<bool name="config_allowTheaterModeWakeFromMotion">true</bool>
+
+ <!-- True if the device supports system decorations on secondary displays. -->
+ <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 37d39a7..5fa13ba 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1346,6 +1346,51 @@
<attr name="materialColorTertiary" format="color"/>
<!-- The error color for the app, intended to draw attention to error conditions. @hide -->
<attr name="materialColorError" format="color"/>
+
+ <!-- System Custom Tokens-->
+ <!-- @hide -->
+ <attr name="customColorWidgetBackground" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorClockHour" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorClockMinute" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorClockSecond" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorThemeApp" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnThemeApp" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorThemeAppRing" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnThemeAppRing" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorBrandA" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorBrandB" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorBrandC" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorBrandD" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorUnderSurface" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorShadeActive" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnShadeActive" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnShadeActiveVariant" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorShadeInactive" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnShadeInactive" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOnShadeInactiveVariant" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorShadeDisabled" format="color"/>
+ <!-- @hide -->
+ <attr name="customColorOverviewBackground" format="color"/>
+
</declare-styleable>
<!-- **************************************************************** -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e671919..5e039b5 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -581,6 +581,51 @@
<color name="system_on_tertiary_fixed">#FFFFFF</color>
<color name="system_on_tertiary_fixed_variant">#FFFFFF</color>
+ <!--Colors used in Android system, from design system. These values can be overlaid at runtime
+ by OverlayManager RROs.-->
+ <color name="system_widget_background_light">#EEF0FF</color>
+ <color name="system_clock_hour_light">#1D2435</color>
+ <color name="system_clock_minute_light">#20386A</color>
+ <color name="system_clock_second_light">#000000</color>
+ <color name="system_theme_app_light">#2F4578</color>
+ <color name="system_on_theme_app_light">#D6DFFF</color>
+ <color name="system_theme_app_ring_light">#94AAE4</color>
+ <color name="system_on_theme_app_ring_light">#FDD7FA</color>
+ <color name="system_brand_a_light">#3A5084</color>
+ <color name="system_brand_b_light">#6E7488</color>
+ <color name="system_brand_c_light">#6076AC</color>
+ <color name="system_brand_d_light">#8C6D8C</color>
+ <color name="system_under_surface_light">#000000</color>
+ <color name="system_shade_active_light">#D9E2FF</color>
+ <color name="system_on_shade_active_light">#152E60</color>
+ <color name="system_on_shade_active_variant_light">#2F4578</color>
+ <color name="system_shade_inactive_light">#2F3036</color>
+ <color name="system_on_shade_inactive_light">#E1E2EC</color>
+ <color name="system_on_shade_inactive_variant_light">#C5C6D0</color>
+ <color name="system_shade_disabled_light">#0C0E13</color>
+ <color name="system_overview_background_light">#50525A</color>
+ <color name="system_widget_background_dark">#152E60</color>
+ <color name="system_clock_hour_dark">#9AA0B6</color>
+ <color name="system_clock_minute_dark">#D8E1FF</color>
+ <color name="system_clock_second_dark">#FFFFFF</color>
+ <color name="system_theme_app_dark">#D9E2FF</color>
+ <color name="system_on_theme_app_dark">#304679</color>
+ <color name="system_theme_app_ring_dark">#94AAE4</color>
+ <color name="system_on_theme_app_ring_dark">#E0BBDD</color>
+ <color name="system_brand_a_dark">#90A6DF</color>
+ <color name="system_brand_b_dark">#A4ABC1</color>
+ <color name="system_brand_c_dark">#7A90C8</color>
+ <color name="system_brand_d_dark">#A886A6</color>
+ <color name="system_under_surface_dark">#000000</color>
+ <color name="system_shade_active_dark">#D9E2FF</color>
+ <color name="system_on_shade_active_dark">#001945</color>
+ <color name="system_on_shade_active_variant_dark">#2F4578</color>
+ <color name="system_shade_inactive_dark">#2F3036</color>
+ <color name="system_on_shade_inactive_dark">#E1E2EC</color>
+ <color name="system_on_shade_inactive_variant_dark">#C5C6D0</color>
+ <color name="system_shade_disabled_dark">#0C0E13</color>
+ <color name="system_overview_background_dark">#C5C6D0</color>
+
<!-- Accessibility shortcut icon background color -->
<color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 -->
<color name="accessibility_magnification_background">#F50D60</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1ef5c3f..5bd2033 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4652,6 +4652,19 @@
-->
<string-array name="config_companionDeviceCerts" translatable="false"></string-array>
+ <!-- A list of packages that auto-enable permissions sync feature.
+ Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+ are parallel arrays.
+ -->
+ <string-array name="config_companionPermSyncEnabledPackages" translatable="false"></string-array>
+
+ <!-- A list of SHA256 Certificates corresponding to config_companionPermSyncEnabledPackages.
+ Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+ are parallel arrays.
+ Example: "1A:2B:3C:4D"
+ -->
+ <string-array name="config_companionPermSyncEnabledCerts" translatable="false"></string-array>
+
<!-- The package name for the default wellbeing app.
This package must be trusted, as it has the permissions to control other applications
on the device.
@@ -4704,6 +4717,13 @@
<!-- The component name for the default system on-device sandboxed inference service. -->
<string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
+ <!-- The broadcast intent name for notifying when the on-device model is loading -->
+ <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string>
+
+ <!-- The broadcast intent name for notifying when the on-device model has been unloaded -->
+ <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string>
+
+
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
<string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8d97362..80cf088 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -27,16 +27,18 @@
<!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
<bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
- <!-- CPU power stats collection throttle period in milliseconds. Since power stats collection
- is a relatively expensive operation, this throttle period may need to be adjusted for low-power
- devices-->
- <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+ <!-- Power stats collection throttle periods in milliseconds. Since power stats collection
+ is a relatively expensive operation, these throttle period may need to be adjusted for low-power
+ devices.
- <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
- <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer>
-
- <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
- <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer>
+ The syntax of this config string is as follows:
+ <pre>
+ power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ...
+ </pre>
+ Use "*" for the power-component-name to represent the default for all power components
+ not mentioned by name.
+ -->
+ <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string>
<!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59e4161..28678c1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -794,6 +794,9 @@
<!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
<string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+ <!-- Text for inline reply button for compact conversation heads ups -->
+ <string name="notification_compact_heads_up_reply">Reply</string>
+
<!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
<string name="notification_hidden_text">New notification</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ec63ea3..acd3b37 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -675,6 +675,8 @@
<java-symbol type="string" name="config_companionDeviceManagerPackage" />
<java-symbol type="array" name="config_companionDevicePackages" />
<java-symbol type="array" name="config_companionDeviceCerts" />
+ <java-symbol type="array" name="config_companionPermSyncEnabledPackages" />
+ <java-symbol type="array" name="config_companionPermSyncEnabledCerts" />
<java-symbol type="string" name="config_default_dns_server" />
<java-symbol type="string" name="config_ethernet_iface_regex" />
<java-symbol type="string" name="not_checked" />
@@ -2351,6 +2353,7 @@
<java-symbol type="layout" name="notification_template_material_base" />
<java-symbol type="layout" name="notification_template_material_heads_up_base" />
<java-symbol type="layout" name="notification_template_material_compact_heads_up_base" />
+ <java-symbol type="layout" name="notification_template_material_messaging_compact_heads_up" />
<java-symbol type="layout" name="notification_template_material_big_base" />
<java-symbol type="layout" name="notification_template_material_big_picture" />
<java-symbol type="layout" name="notification_template_material_inbox" />
@@ -3626,6 +3629,7 @@
<java-symbol type="drawable" name="lockscreen_selected" />
<java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
+ <java-symbol type="string" name="notification_compact_heads_up_reply" />
<java-symbol type="color" name="notification_primary_text_color_light" />
<java-symbol type="color" name="notification_primary_text_color_dark" />
@@ -3941,6 +3945,8 @@
<java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
<java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
+ <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" />
+ <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
@@ -4518,6 +4524,7 @@
<java-symbol type="id" name="expand_button_container" />
<java-symbol type="id" name="expand_button_a11y_container" />
<java-symbol type="id" name="expand_button_touch_container" />
+ <java-symbol type="id" name="reply_action_container" />
<java-symbol type="id" name="messaging_group_content_container" />
<java-symbol type="id" name="expand_button_and_content_container" />
<java-symbol type="id" name="conversation_header" />
@@ -5236,9 +5243,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" />
+ <java-symbol type="string" name="config_powerStatsThrottlePeriods" />
<java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
<java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
@@ -5288,6 +5293,71 @@
<java-symbol name="materialColorTertiary" type="attr"/>
<java-symbol name="materialColorError" type="attr"/>
+ <java-symbol name="customColorWidgetBackground" type="attr"/>
+ <java-symbol name="customColorClockHour" type="attr"/>
+ <java-symbol name="customColorClockMinute" type="attr"/>
+ <java-symbol name="customColorClockSecond" type="attr"/>
+ <java-symbol name="customColorThemeApp" type="attr"/>
+ <java-symbol name="customColorOnThemeApp" type="attr"/>
+ <java-symbol name="customColorThemeAppRing" type="attr"/>
+ <java-symbol name="customColorOnThemeAppRing" type="attr"/>
+ <java-symbol name="customColorBrandA" type="attr"/>
+ <java-symbol name="customColorBrandB" type="attr"/>
+ <java-symbol name="customColorBrandC" type="attr"/>
+ <java-symbol name="customColorBrandD" type="attr"/>
+ <java-symbol name="customColorUnderSurface" type="attr"/>
+ <java-symbol name="customColorShadeActive" type="attr"/>
+ <java-symbol name="customColorOnShadeActive" type="attr"/>
+ <java-symbol name="customColorOnShadeActiveVariant" type="attr"/>
+ <java-symbol name="customColorShadeInactive" type="attr"/>
+ <java-symbol name="customColorOnShadeInactive" type="attr"/>
+ <java-symbol name="customColorOnShadeInactiveVariant" type="attr"/>
+ <java-symbol name="customColorShadeDisabled" type="attr"/>
+ <java-symbol name="customColorOverviewBackground" type="attr"/>
+
+ <java-symbol name="system_widget_background_light" type="color"/>
+ <java-symbol name="system_clock_hour_light" type="color"/>
+ <java-symbol name="system_clock_minute_light" type="color"/>
+ <java-symbol name="system_clock_second_light" type="color"/>
+ <java-symbol name="system_theme_app_light" type="color"/>
+ <java-symbol name="system_on_theme_app_light" type="color"/>
+ <java-symbol name="system_theme_app_ring_light" type="color"/>
+ <java-symbol name="system_on_theme_app_ring_light" type="color"/>
+ <java-symbol name="system_brand_a_light" type="color"/>
+ <java-symbol name="system_brand_b_light" type="color"/>
+ <java-symbol name="system_brand_c_light" type="color"/>
+ <java-symbol name="system_brand_d_light" type="color"/>
+ <java-symbol name="system_under_surface_light" type="color"/>
+ <java-symbol name="system_shade_active_light" type="color"/>
+ <java-symbol name="system_on_shade_active_light" type="color"/>
+ <java-symbol name="system_on_shade_active_variant_light" type="color"/>
+ <java-symbol name="system_shade_inactive_light" type="color"/>
+ <java-symbol name="system_on_shade_inactive_light" type="color"/>
+ <java-symbol name="system_on_shade_inactive_variant_light" type="color"/>
+ <java-symbol name="system_shade_disabled_light" type="color"/>
+ <java-symbol name="system_overview_background_light" type="color"/>
+ <java-symbol name="system_widget_background_dark" type="color"/>
+ <java-symbol name="system_clock_hour_dark" type="color"/>
+ <java-symbol name="system_clock_minute_dark" type="color"/>
+ <java-symbol name="system_clock_second_dark" type="color"/>
+ <java-symbol name="system_theme_app_dark" type="color"/>
+ <java-symbol name="system_on_theme_app_dark" type="color"/>
+ <java-symbol name="system_theme_app_ring_dark" type="color"/>
+ <java-symbol name="system_on_theme_app_ring_dark" type="color"/>
+ <java-symbol name="system_brand_a_dark" type="color"/>
+ <java-symbol name="system_brand_b_dark" type="color"/>
+ <java-symbol name="system_brand_c_dark" type="color"/>
+ <java-symbol name="system_brand_d_dark" type="color"/>
+ <java-symbol name="system_under_surface_dark" type="color"/>
+ <java-symbol name="system_shade_active_dark" type="color"/>
+ <java-symbol name="system_on_shade_active_dark" type="color"/>
+ <java-symbol name="system_on_shade_active_variant_dark" type="color"/>
+ <java-symbol name="system_shade_inactive_dark" type="color"/>
+ <java-symbol name="system_on_shade_inactive_dark" type="color"/>
+ <java-symbol name="system_on_shade_inactive_variant_dark" type="color"/>
+ <java-symbol name="system_shade_disabled_dark" type="color"/>
+ <java-symbol name="system_overview_background_dark" type="color"/>
+
<java-symbol type="attr" name="actionModeUndoDrawable" />
<java-symbol type="attr" name="actionModeRedoDrawable" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ee19144..24d4938 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -284,6 +284,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
@@ -380,6 +402,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme
@@ -475,6 +519,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
@@ -572,6 +638,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
@@ -668,6 +756,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
@@ -772,6 +882,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
@@ -867,6 +999,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
@@ -961,6 +1115,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
@@ -1056,6 +1232,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -1167,6 +1365,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for a window without an action bar that will be displayed either
@@ -1263,6 +1483,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for a presentation window on a secondary display. -->
@@ -1357,6 +1599,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for panel windows. This removes all extraneous window
@@ -1453,6 +1717,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -1548,6 +1834,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -1643,6 +1951,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -1738,6 +2068,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -1833,6 +2185,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -1928,6 +2302,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Theme for the dialog shown when an app crashes or ANRs. -->
@@ -2028,6 +2424,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
@@ -2121,6 +2539,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
@@ -2352,6 +2792,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
@@ -2447,6 +2909,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
@@ -2541,6 +3025,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar.
@@ -2636,6 +3142,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar
@@ -2733,6 +3261,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent
@@ -2829,6 +3379,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
@@ -2931,6 +3503,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} that has a nice minimum width for a
@@ -3029,6 +3623,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
@@ -3126,6 +3742,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
@@ -3224,6 +3862,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -3303,6 +3963,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. -->
@@ -3382,6 +4064,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller
@@ -3480,6 +4184,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault light theme for a window without an action bar that will be displayed either
@@ -3579,6 +4305,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault light theme for a presentation window on a secondary display. -->
@@ -3676,6 +4424,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault light theme for panel windows. This removes all extraneous window
@@ -3772,6 +4542,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
@@ -3867,6 +4659,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -3962,6 +4776,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
@@ -4055,6 +4891,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
@@ -4156,6 +5014,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.SystemUI" parent="Theme.DeviceDefault.Light">
@@ -4238,6 +5118,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.SystemUI.Dialog" parent="Theme.DeviceDefault.Light.Dialog">
@@ -4312,6 +5214,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Settings_Dark} with no action bar -->
@@ -4407,6 +5331,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="Theme.DeviceDefault.Settings.DialogBase" parent="Theme.Material.Light.BaseDialog">
@@ -4486,6 +5432,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Settings.DialogBase">
@@ -4605,6 +5573,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -4702,6 +5692,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
@@ -4825,6 +5837,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="ThemeOverlay.DeviceDefault.Accent.Light">
@@ -4878,6 +5912,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. -->
@@ -4935,6 +5991,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen">
@@ -4988,6 +6066,28 @@
<item name="materialColorSecondary">@color/system_secondary_light</item>
<item name="materialColorTertiary">@color/system_tertiary_light</item>
<item name="materialColorError">@color/system_error_light</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+ <item name="customColorClockHour">@color/system_clock_hour_light</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+ <item name="customColorClockSecond">@color/system_clock_second_light</item>
+ <item name="customColorThemeApp">@color/system_theme_app_light</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+ <item name="customColorBrandA">@color/system_brand_a_light</item>
+ <item name="customColorBrandB">@color/system_brand_b_light</item>
+ <item name="customColorBrandC">@color/system_brand_c_light</item>
+ <item name="customColorBrandD">@color/system_brand_d_light</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+ <item name="customColorShadeActive">@color/system_shade_active_light</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
</style>
<style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification">
@@ -5052,6 +6152,28 @@
<item name="materialColorSecondary">@color/system_secondary_dark</item>
<item name="materialColorTertiary">@color/system_tertiary_dark</item>
<item name="materialColorError">@color/system_error_dark</item>
+
+ <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+ <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+ <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+ <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+ <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+ <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+ <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+ <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+ <item name="customColorBrandA">@color/system_brand_a_dark</item>
+ <item name="customColorBrandB">@color/system_brand_b_dark</item>
+ <item name="customColorBrandC">@color/system_brand_c_dark</item>
+ <item name="customColorBrandD">@color/system_brand_d_dark</item>
+ <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+ <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+ <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+ <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+ <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+ <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+ <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+ <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
</style>
<style name="Theme.DeviceDefault.AutofillHalfScreenDialogList" parent="Theme.DeviceDefault.DayNight">
<item name="colorListDivider">@color/list_divider_opacity_device_default_light</item>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 436ba15..eb3c84a 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -260,6 +260,7 @@
"src/com/android/internal/os/**/*.java",
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
+ "src/com/android/internal/ravenwood/**/*.java",
// Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
// to avoid having a dependency to FrameworksCoreTests.
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index b6f4429..ee1d1e1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -65,6 +66,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer;
/**
@@ -221,4 +223,17 @@
123 /* newDisplayId */, true /* shouldReportConfigChange*/);
inOrder.verify(mController).onContextConfigurationPostChanged(context);
}
+
+ @Test
+ public void testDisplayListenerHandlerClosed() {
+ doReturn(123).when(mActivity).getDisplayId();
+ doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123);
+
+ mController.onContextConfigurationPreChanged(mActivity);
+ mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+ mController.onContextConfigurationPostChanged(mActivity);
+
+ // No crash
+ verify(mController).onDisplayChanged(123);
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 950925f..e0c3b04 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -475,8 +475,8 @@
final Icon ic = Icon.createWithBitmap(bm);
final Drawable drawable = ic.loadDrawable(mContext);
- assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
- assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
+ assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2);
+ assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2);
}
@Test
diff --git a/core/tests/coretests/src/android/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS
index a779c00..beb77dc 100644
--- a/core/tests/coretests/src/android/net/OWNERS
+++ b/core/tests/coretests/src/android/net/OWNERS
@@ -1,4 +1,5 @@
include /services/core/java/com/android/server/net/OWNERS
-per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
+per-file SSL*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
+per-file Uri* = varunshah@google.com
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 2a4ca79..57cb158 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -18,6 +18,7 @@
import android.content.ContentUris;
import android.os.Parcel;
+import android.platform.test.annotations.AsbSecurityTest;
import androidx.test.filters.SmallTest;
@@ -86,6 +87,16 @@
assertNull(u.getHost());
}
+ @AsbSecurityTest(cveBugId = 261721900)
+ @SmallTest
+ public void testSchemeSanitization() {
+ Uri uri = new Uri.Builder()
+ .scheme("http://https://evil.com:/te:st/")
+ .authority("google.com").path("one/way").build();
+ assertEquals("httphttpsevil.com:/te:st/", uri.getScheme());
+ assertEquals("httphttpsevil.com:/te:st/://google.com/one/way", uri.toString());
+ }
+
@SmallTest
public void testStringUri() {
assertEquals("bob lee",
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index 6ae3d65..df9a89e 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -674,8 +674,6 @@
protoOutputStream.write(SINGLE_INT, singleIntValue);
protoOutputStream.end(payloadToken);
protoOutputStream.end(forTestingToken);
-
- ctx.flush();
}),
(args) -> {}
);
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 57bbb1c..5917cc1 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.graphics.Insets;
import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
import android.view.animation.BackGestureInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputMethodManager;
@@ -54,6 +55,8 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.lang.reflect.Field;
+
/**
* Tests for {@link ImeBackAnimationController}.
*
@@ -104,6 +107,13 @@
when(mInsetsController.getHost()).thenReturn(mViewRootInsetsControllerHost);
when(mViewRootInsetsControllerHost.getInputMethodManager()).thenReturn(
inputMethodManager);
+ try {
+ Field field = InsetsController.class.getDeclaredField("mSourceConsumers");
+ field.setAccessible(true);
+ field.set(mInsetsController, new SparseArray<InsetsSourceConsumer>());
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException("Unable to set mSourceConsumers", e);
+ }
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index bc0ae9f..0b1b40c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -623,28 +623,6 @@
assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
}
- @Test
- @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
- })
- public void idleDetected() throws Throwable {
- waitForFrameRateCategoryToSettle();
- mActivityRule.runOnUiThread(() -> {
- mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
- mMovingView.setFrameContentVelocity(Float.MAX_VALUE);
- mMovingView.invalidate();
- runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
- mViewRoot.getLastPreferredFrameRateCategory()));
- });
- waitForAfterDraw();
-
- // Wait for idle timeout
- Thread.sleep(500);
- assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
- assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
- mViewRoot.getLastPreferredFrameRateCategory());
- }
-
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
new file mode 100644
index 0000000..4eccbe5
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.content.res;
+
+import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.om.OverlayConfigParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigParserTest {
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropNotRoProp() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${persist.value}/path", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropMissingEndBracket() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${ro.value/path", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergeOnlyPropStart() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("path/${", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropInProp() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp);
+ }
+
+ /**
+ * The path is only allowed to contain one property.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropMultipleProps() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp);
+ }
+
+ @Test
+ public void testMergePropOneProp() {
+ final SysPropWrapper sysProp = p -> {
+ if ("ro.value".equals(p)) {
+ return "dummy_value";
+ } else {
+ return "invalid";
+ }
+ };
+
+ // Property in the beginnig of the string
+ String result = OverlayConfigParser.expandProperty("${ro.value}/path",
+ sysProp);
+ assertEquals("dummy_value/path", result);
+
+ // Property in the middle of the string
+ result = OverlayConfigParser.expandProperty("path/${ro.value}/file",
+ sysProp);
+ assertEquals("path/dummy_value/file", result);
+
+ // Property at the of the string
+ result = OverlayConfigParser.expandProperty("path/${ro.value}",
+ sysProp);
+ assertEquals("path/dummy_value", result);
+
+ // Property is the entire string
+ result = OverlayConfigParser.expandProperty("${ro.value}",
+ sysProp);
+ assertEquals("dummy_value", result);
+ }
+
+ @Test
+ public void testMergePropNoProp() {
+ final SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+
+ final String path = "no_props/path";
+ String result = OverlayConfigParser.expandProperty(path, sysProp);
+ assertEquals(path, result);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index baab3b2..4846ed27 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -22,6 +22,7 @@
import android.os.Parcel;
import android.os.PersistableBundle;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.util.Xml;
@@ -31,6 +32,8 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.google.common.truth.StringSubject;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -38,6 +41,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
@RunWith(AndroidJUnit4.class)
@@ -56,6 +60,9 @@
extras.putBoolean("hasPowerMonitor", true);
SparseArray<String> stateLabels = new SparseArray<>();
stateLabels.put(0x0F, "idle");
+ extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]");
+ extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0");
+ extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1");
mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels,
1, 2, extras);
mRegistry.register(mDescriptor);
@@ -191,4 +198,68 @@
newParcel.setDataPosition(0);
return newParcel;
}
+
+ @Test
+ public void formatForBatteryHistory() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.stateStats.put(0x0F, new long[]{16});
+ stats.stateStats.put(0xF0, new long[]{17});
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ assertThat(stats.formatForBatteryHistory(" #"))
+ .isEqualTo("duration=1234 cpu="
+ + "device: [10, 20, 30]"
+ + " (idle) state: 16"
+ + " (cpu-f0) state: 17"
+ + " #42: a: 40 b: 50"
+ + " #99: a: 60 b: 70");
+ }
+
+ @Test
+ public void dump() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.stateStats.put(0x0F, new long[]{16});
+ stats.stateStats.put(0xF0, new long[]{17});
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+ stats.dump(pw);
+ pw.flush();
+ String dump = sw.toString();
+
+ assertThat(dump).contains("duration=1234");
+ assertThat(dump).contains("device: [10, 20, 30]");
+ assertThat(dump).contains("(idle) state: 16");
+ assertThat(dump).contains("(cpu-f0) state: 17");
+ assertThat(dump).contains("UID 42: a: 40 b: 50");
+ assertThat(dump).contains("UID 99: a: 60 b: 70");
+ }
+
+ @Test
+ public void formatter() {
+ assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]")
+ .isEqualTo("a: 12 b: [34, 56]");
+ assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?")
+ .isEqualTo("a: 12");
+ assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?")
+ .isEqualTo("b: [34, 56]");
+ assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p")
+ .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]");
+ }
+
+ private static StringSubject assertThatFormatted(long[] stats, String format) {
+ return assertThat(new PowerStats.PowerStatsFormatter(format)
+ .format(stats));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
new file mode 100644
index 0000000..d1ef61b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.ravenwood;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodEnvironmentTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testIsRunningOnRavenwood() {
+ assertEquals(RavenwoodRule.isUnderRavenwood(),
+ RavenwoodEnvironment.getInstance().isRunningOnRavenwood());
+ }
+}
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 000f6ef..b41a607 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 01deb49..b93cd46 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -583,6 +583,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "7211222997110112110": {
+ "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRefresher.java"
+ },
"1665699123574159131": {
"message": "Starting activity when config will change = %b",
"level": "VERBOSE",
@@ -1771,12 +1777,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
},
- "-7756685416834187936": {
- "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
- },
"-5176775281239247368": {
"message": "Reverting orientation after camera compat force rotation",
"level": "VERBOSE",
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index ac1b064..a4ee825 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -768,7 +768,7 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"
supportedAxes="wght">
NotoSansCJK-Regular.ttc
<!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -780,7 +780,7 @@
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"
supportedAxes="wght">
NotoSansCJK-Regular.ttc
<!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -792,7 +792,7 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"
supportedAxes="wght">
NotoSansCJK-Regular.ttc
<!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -810,7 +810,7 @@
</font>
</family>
<family lang="ko">
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"
supportedAxes="wght">
NotoSansCJK-Regular.ttc
<!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 9545ae7..8cbc300 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1409,39 +1409,39 @@
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
</family>
<family lang="zh-Hans">
- <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="100"/>
</font>
- <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="200"/>
</font>
- <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="300"/>
</font>
- <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="500"/>
</font>
- <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="600"/>
</font>
- <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="700"/>
</font>
- <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="800"/>
</font>
- <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="900"/>
</font>
@@ -1450,39 +1450,39 @@
</font>
</family>
<family lang="zh-Hant,zh-Bopo">
- <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="100"/>
</font>
- <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="200"/>
</font>
- <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="300"/>
</font>
- <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="500"/>
</font>
- <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="600"/>
</font>
- <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="700"/>
</font>
- <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="800"/>
</font>
- <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="900"/>
</font>
@@ -1491,39 +1491,39 @@
</font>
</family>
<family lang="ja">
- <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="100"/>
</font>
- <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="200"/>
</font>
- <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="300"/>
</font>
- <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="500"/>
</font>
- <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="600"/>
</font>
- <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="700"/>
</font>
- <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="800"/>
</font>
- <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="900"/>
</font>
@@ -1542,39 +1542,39 @@
</font>
</family>
<family lang="ko">
- <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="100"/>
</font>
- <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="200"/>
</font>
- <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="300"/>
</font>
- <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="500"/>
</font>
- <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="600"/>
</font>
- <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="700"/>
</font>
- <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="800"/>
</font>
- <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
NotoSansCJK-Regular.ttc
<axis tag="wght" stylevalue="900"/>
</font>
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index e8b4104..f8d3bff 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -438,9 +438,15 @@
key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING
key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING
key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING
key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING
key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING
+key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING
key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING
+key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING
+key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING
+key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING
+key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING
key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING
key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index d915b74..1c20141 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -143,7 +143,7 @@
* the decoder will try to pick the best matching config based on the
* system's screen depth, and characteristics of the original image such
* as if it has per-pixel alpha (requiring a config that also does).
- *
+ *
* Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
* default.
*/
@@ -183,7 +183,7 @@
/**
* If true (which is the default), the resulting bitmap will have its
- * color channels pre-multipled by the alpha channel.
+ * color channels pre-multiplied by the alpha channel.
*
* <p>This should NOT be set to false for images to be directly drawn by
* the view system or through a {@link Canvas}. The view system and
@@ -221,9 +221,9 @@
* if {@link #inScaled} is set (which it is by default} and this
* density does not match {@link #inTargetDensity}, then the bitmap
* will be scaled to the target density before being returned.
- *
+ *
* <p>If this is 0,
- * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int)},
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
* and {@link BitmapFactory#decodeResourceStream}
* will fill in the density associated with the resource. The other
@@ -242,29 +242,29 @@
* This is used in conjunction with {@link #inDensity} and
* {@link #inScaled} to determine if and how to scale the bitmap before
* returning it.
- *
+ *
* <p>If this is 0,
- * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int)},
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
* and {@link BitmapFactory#decodeResourceStream}
* will fill in the density associated the Resources object's
* DisplayMetrics. The other
* functions will leave it as-is and no scaling for density will be
* performed.
- *
+ *
* @see #inDensity
* @see #inScreenDensity
* @see #inScaled
* @see android.util.DisplayMetrics#densityDpi
*/
public int inTargetDensity;
-
+
/**
* The pixel density of the actual screen that is being used. This is
* purely for applications running in density compatibility code, where
* {@link #inTargetDensity} is actually the density the application
* sees rather than the real screen density.
- *
+ *
* <p>By setting this, you
* allow the loading code to avoid scaling a bitmap that is currently
* in the screen density up/down to the compatibility density. Instead,
@@ -274,18 +274,18 @@
* Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
* Bitmap.getScaledHeight} to account for any different between the
* bitmap's density and the target's density.
- *
+ *
* <p>This is never set automatically for the caller by
* {@link BitmapFactory} itself. It must be explicitly set, since the
* caller must deal with the resulting bitmap in a density-aware way.
- *
+ *
* @see #inDensity
* @see #inTargetDensity
* @see #inScaled
* @see android.util.DisplayMetrics#densityDpi
*/
public int inScreenDensity;
-
+
/**
* When this flag is set, if {@link #inDensity} and
* {@link #inTargetDensity} are not 0, the
@@ -345,7 +345,7 @@
* ignored.
*
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
- * field works in conjuction with inPurgeable. If inPurgeable is false,
+ * field works in conjunction with inPurgeable. If inPurgeable is false,
* then this field is ignored. If inPurgeable is true, then this field
* determines whether the bitmap can share a reference to the input
* data (inputstream, array, etc.) or if it must make a deep copy.
@@ -583,11 +583,11 @@
opts.inDensity = density;
}
}
-
+
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
-
+
return decodeStream(is, pad, opts);
}
@@ -611,8 +611,8 @@
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
- InputStream is = null;
-
+ InputStream is = null;
+
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e03a1da..0b3e545 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -55,7 +55,7 @@
* Canvas and Drawables</a> developer guide.</p></div>
*/
public class Canvas extends BaseCanvas {
- private static int sCompatiblityVersion = 0;
+ private static int sCompatibilityVersion = 0;
private static boolean sCompatibilityRestore = false;
private static boolean sCompatibilitySetBitmap = false;
@@ -74,7 +74,7 @@
// Maximum bitmap size as defined in Skia's native code
// (see SkCanvas.cpp, SkDraw.cpp)
- private static final int MAXMIMUM_BITMAP_SIZE = 32766;
+ private static final int MAXIMUM_BITMAP_SIZE = 32766;
// Use a Holder to allow static initialization of Canvas in the boot image.
private static class NoImagePreloadHolder {
@@ -331,7 +331,7 @@
* @see #getMaximumBitmapHeight()
*/
public int getMaximumBitmapWidth() {
- return MAXMIMUM_BITMAP_SIZE;
+ return MAXIMUM_BITMAP_SIZE;
}
/**
@@ -342,7 +342,7 @@
* @see #getMaximumBitmapWidth()
*/
public int getMaximumBitmapHeight() {
- return MAXMIMUM_BITMAP_SIZE;
+ return MAXIMUM_BITMAP_SIZE;
}
// the SAVE_FLAG constants must match their native equivalents
@@ -423,7 +423,7 @@
public static final int ALL_SAVE_FLAG = 0x1F;
private static void checkValidSaveFlags(int saveFlags) {
- if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ if (sCompatibilityVersion >= Build.VERSION_CODES.P
&& saveFlags != ALL_SAVE_FLAG) {
throw new IllegalArgumentException(
"Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed");
@@ -845,7 +845,7 @@
}
private static void checkValidClipOp(@NonNull Region.Op op) {
- if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ if (sCompatibilityVersion >= Build.VERSION_CODES.P
&& op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) {
throw new IllegalArgumentException(
"Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed");
@@ -1435,7 +1435,7 @@
}
/*package*/ static void setCompatibilityVersion(int apiLevel) {
- sCompatiblityVersion = apiLevel;
+ sCompatibilityVersion = apiLevel;
sCompatibilityRestore = apiLevel < Build.VERSION_CODES.M;
sCompatibilitySetBitmap = apiLevel < Build.VERSION_CODES.O;
nSetCompatibilityVersion(apiLevel);
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 0f2f879..c1edafc 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -289,6 +289,9 @@
*/
@AnyThread
@SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public class Color {
@ColorInt public static final int BLACK = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index a2319a5..4bc3ece 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -135,6 +135,9 @@
@AnyThread
@SuppressWarnings("StaticInitializerReferencesSubClass")
@SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public abstract class ColorSpace {
/**
* Standard CIE 1931 2° illuminant A, encoded in xyY.
@@ -2490,9 +2493,16 @@
return mNativePtr;
}
- private static native long nativeGetNativeFinalizer();
- private static native long nativeCreate(float a, float b, float c, float d,
- float e, float f, float g, float[] xyz);
+ /**
+ * These methods can't be put in the Rgb class directly, because ColorSpace's
+ * static initializer instantiates Rgb, whose constructor needs them, which is a variation
+ * of b/337329128.
+ */
+ static class Native {
+ static native long nativeGetNativeFinalizer();
+ static native long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz);
+ }
private static DoubleUnaryOperator generateOETF(TransferParameters function) {
if (function.isHLGish()) {
@@ -2959,7 +2969,7 @@
// This mimics the old code that was in native.
float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
- mNativePtr = nativeCreate((float) mTransferParameters.a,
+ mNativePtr = Native.nativeCreate((float) mTransferParameters.a,
(float) mTransferParameters.b,
(float) mTransferParameters.c,
(float) mTransferParameters.d,
@@ -2975,7 +2985,7 @@
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
+ ColorSpace.Rgb.class.getClassLoader(), Native.nativeGetNativeFinalizer(), 0);
}
/**
diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java
index 3fc9eb5..7d59ece 100644
--- a/graphics/java/android/graphics/ComposePathEffect.java
+++ b/graphics/java/android/graphics/ComposePathEffect.java
@@ -20,13 +20,13 @@
/**
* Construct a PathEffect whose effect is to apply first the inner effect
- * and the the outer pathEffect (e.g. outer(inner(path))).
+ * and the outer pathEffect (e.g. outer(inner(path))).
*/
public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) {
native_instance = nativeCreate(outerpe.native_instance,
innerpe.native_instance);
}
-
+
private static native long nativeCreate(long nativeOuterpe,
long nativeInnerpe);
}
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index c86b744..88f0e8e 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -219,7 +219,7 @@
@CriticalNative
private static native long nGetFamilyReleaseFunc();
- // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+ // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font.
// By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
int weight, int isItalic);
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 17c2dd9..13c4a94 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -127,7 +127,7 @@
parser.setInput(is, null);
parser.nextTag();
return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
- lastModifiedDate, configVersion, false /* filter out the non-exising files */);
+ lastModifiedDate, configVersion, false /* filter out the non-existing files */);
}
}
@@ -254,7 +254,7 @@
* @param parser An XML parser.
* @param fontDir a font directory name.
* @param updatableFontMap a updated font file map.
- * @param allowNonExistingFile true to allow font file that doesn't exists
+ * @param allowNonExistingFile true to allow font file that doesn't exist.
* @return a FontFamily instance. null if no font files are available in this FontFamily.
*/
public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index b3615ff..8f12828 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -24,7 +24,7 @@
/**
* Class that contains all the timing information for the current frame. This
* is used in conjunction with the hardware renderer to provide
- * continous-monitoring jank events
+ * continuous-monitoring jank events
*
* All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
*
diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java
index 994fb2d..28f296d 100644
--- a/graphics/java/android/graphics/Interpolator.java
+++ b/graphics/java/android/graphics/Interpolator.java
@@ -28,13 +28,13 @@
mFrameCount = 2;
native_instance = nativeConstructor(valueCount, 2);
}
-
+
public Interpolator(int valueCount, int frameCount) {
mValueCount = valueCount;
mFrameCount = frameCount;
native_instance = nativeConstructor(valueCount, frameCount);
}
-
+
/**
* Reset the Interpolator to have the specified number of values and an
* implicit keyFrame count of 2 (just a start and end). After this call the
@@ -43,7 +43,7 @@
public void reset(int valueCount) {
reset(valueCount, 2);
}
-
+
/**
* Reset the Interpolator to have the specified number of values and
* keyFrames. After this call the values for each keyFrame must be assigned
@@ -54,20 +54,20 @@
mFrameCount = frameCount;
nativeReset(native_instance, valueCount, frameCount);
}
-
+
public final int getKeyFrameCount() {
return mFrameCount;
}
-
+
public final int getValueCount() {
return mValueCount;
}
-
+
/**
* Assign the keyFrame (specified by index) a time value and an array of key
- * values (with an implicity blend array of [0, 0, 1, 1] giving linear
+ * values (with an implicitly blend array of [0, 0, 1, 1] giving linear
* transition to the next set of key values).
- *
+ *
* @param index The index of the key frame to assign
* @param msec The time (in mililiseconds) for this key frame. Based on the
* SystemClock.uptimeMillis() clock
@@ -80,7 +80,7 @@
/**
* Assign the keyFrame (specified by index) a time value and an array of key
* values and blend array.
- *
+ *
* @param index The index of the key frame to assign
* @param msec The time (in mililiseconds) for this key frame. Based on the
* SystemClock.uptimeMillis() clock
@@ -99,7 +99,7 @@
}
nativeSetKeyFrame(native_instance, index, msec, values, blend);
}
-
+
/**
* Set a repeat count (which may be fractional) for the interpolator, and
* whether the interpolator should mirror its repeats. The default settings
@@ -110,7 +110,7 @@
nativeSetRepeatMirror(native_instance, repeatCount, mirror);
}
}
-
+
public enum Result {
NORMAL,
FREEZE_START,
@@ -130,7 +130,7 @@
* return whether the specified time was within the range of key times
* (NORMAL), was before the first key time (FREEZE_START) or after the last
* key time (FREEZE_END). In any event, computed values are always returned.
- *
+ *
* @param msec The time (in milliseconds) used to sample into the
* Interpolator. Based on the SystemClock.uptimeMillis() clock
* @param values Where to write the computed values (may be NULL).
@@ -146,13 +146,13 @@
default: return Result.FREEZE_END;
}
}
-
+
@Override
protected void finalize() throws Throwable {
nativeDestructor(native_instance);
native_instance = 0; // Other finalizers can still call us.
}
-
+
private int mValueCount;
private int mFrameCount;
private long native_instance;
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 0aa6f12..fe73a1a 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkColorFilter.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 56d912b..0879371 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -63,7 +63,7 @@
* @param colors The sRGB colors to be distributed along the gradient line
* @param positions May be null. The relative positions [0..1] of
* each corresponding color in the colors array. If this is null,
- * the the colors are distributed evenly along the gradient line.
+ * the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
*/
public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors,
@@ -82,7 +82,7 @@
* @param colors The colors to be distributed along the gradient line
* @param positions May be null. The relative positions [0..1] of
* each corresponding color in the colors array. If this is null,
- * the the colors are distributed evenly along the gradient line.
+ * the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
*
* @throws IllegalArgumentException if there are less than two colors, the colors do
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index fbb690c..748e9dd 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -232,7 +232,7 @@
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
- Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper());
+ Matrix.class.getClassLoader(), ExtraNatives.nGetNativeFinalizer());
}
private final long native_instance;
@@ -241,7 +241,7 @@
* Create an identity matrix
*/
public Matrix() {
- native_instance = nCreateWrapper(0);
+ native_instance = ExtraNatives.nCreate(0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -251,7 +251,7 @@
* @param src The matrix to copy into this matrix
*/
public Matrix(Matrix src) {
- native_instance = nCreateWrapper(src != null ? src.native_instance : 0);
+ native_instance = ExtraNatives.nCreate(src != null ? src.native_instance : 0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -562,7 +562,7 @@
/**
* Set the matrix to the scale and translate values that map the source rectangle to the
- * destination rectangle, returning true if the the result can be represented.
+ * destination rectangle, returning true if the result can be represented.
*
* @param src the source rectangle to map from.
* @param dst the destination rectangle to map to.
@@ -849,40 +849,6 @@
return native_instance;
}
- /**
- * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood.
- *
- * @see ExtraNatives
- */
- @android.ravenwood.annotation.RavenwoodReplace
- private static long nCreateWrapper(long src) {
- return nCreate(src);
- }
-
- private static long nCreateWrapper$ravenwood(long src) {
- return ExtraNatives.nCreate(src);
- }
-
- /**
- * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood.
- *
- * @see ExtraNatives
- */
- @android.ravenwood.annotation.RavenwoodReplace
- private static long nGetNativeFinalizerWrapper() {
- return nGetNativeFinalizer();
- }
-
- private static long nGetNativeFinalizerWrapper$ravenwood() {
- return ExtraNatives.nGetNativeFinalizer();
- }
-
- // ------------------ Regular JNI ------------------------
-
- private static native long nCreate(long nSrc_or_zero);
- private static native long nGetNativeFinalizer();
-
-
// ------------------ Fast JNI ------------------------
@FastNative
@@ -982,14 +948,6 @@
* There are two methods that are called by the static initializers (either directly or
* indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood
* these methods can't be on the Matrix class itself, so we use a nested class to host them.
- *
- * We still keep the original nCreate() method and call it on non-ravenwood environment,
- * in order to avoid problems in downstream (such as Android Studio).
- *
- * @see #nCreateWrapper(long)
- * @see #nGetNativeFinalizerWrapper()
- *
- * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?)
*/
private static class ExtraNatives {
static native long nCreate(long nSrc_or_zero);
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index a4bce9e..6be8332 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -271,7 +271,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than int
* or int[1] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value value corresponding to the int uniform with the given name.
*/
public void setIntUniform(@NonNull String uniformName, int value) {
@@ -283,7 +283,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec2
* or int[2] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
*/
@@ -296,7 +296,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec3
* or int[3] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
@@ -310,7 +310,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec4
* or int[4] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
@@ -327,7 +327,7 @@
* int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
* IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
*/
public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index af20957..382269f 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -22,12 +22,12 @@
* The NinePatch class permits drawing a bitmap in nine or more sections.
* Essentially, it allows the creation of custom graphics that will scale the
* way that you define, when content added within the image exceeds the normal
- * bounds of the graphic. For a thorough explanation of a NinePatch image,
- * read the discussion in the
+ * bounds of the graphic. For a thorough explanation of a NinePatch image,
+ * read the discussion in the
* <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D
* Graphics</a> document.
* <p>
- * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a>
+ * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a>
* tool offers an extremely handy way to create your NinePatch images,
* using a WYSIWYG graphics editor.
* </p>
@@ -104,7 +104,7 @@
this(bitmap, chunk, null);
}
- /**
+ /**
* Create a drawable projection from a bitmap to nine patches.
*
* @param bitmap The bitmap describing the patches.
@@ -122,7 +122,7 @@
protected void finalize() throws Throwable {
try {
if (mNativeChunk != 0) {
- // only attempt to destroy correctly initilized chunks
+ // only attempt to destroy correctly initialized chunks
nativeFinalize(mNativeChunk);
mNativeChunk = 0;
}
@@ -169,8 +169,8 @@
public Bitmap getBitmap() {
return mBitmap;
}
-
- /**
+
+ /**
* Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
*
* @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -180,7 +180,7 @@
canvas.drawPatch(this, location, mPaint);
}
- /**
+ /**
* Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
*
* @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -190,7 +190,7 @@
canvas.drawPatch(this, location, mPaint);
}
- /**
+ /**
* Draws the NinePatch. This method will ignore the paint returned
* by {@link #getPaint()} and use the specified paint instead.
*
diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java
index 5500c52..2c6cfa5 100644
--- a/graphics/java/android/graphics/PathMeasure.java
+++ b/graphics/java/android/graphics/PathMeasure.java
@@ -25,14 +25,14 @@
* setPath.
*
* Note that once a path is associated with the measure object, it is
- * undefined if the path is subsequently modified and the the measure object
+ * undefined if the path is subsequently modified and the measure object
* is used. If the path is modified, you must call setPath with the path.
*/
public PathMeasure() {
mPath = null;
native_instance = native_create(0, false);
}
-
+
/**
* Create a PathMeasure object associated with the specified path object
* (already created and specified). The measure object can now return the
@@ -40,7 +40,7 @@
* path.
*
* Note that once a path is associated with the measure object, it is
- * undefined if the path is subsequently modified and the the measure object
+ * undefined if the path is subsequently modified and the measure object
* is used. If the path is modified, you must call setPath with the path.
*
* @param path The path that will be measured by this object
@@ -121,7 +121,7 @@
* such as <code>dst.rLineTo(0, 0)</code>.</p>
*/
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
- // Skia used to enforce this as part of it's API, but has since relaxed that restriction
+ // Skia used to enforce this as part of its API, but has since relaxed that restriction
// so to maintain consistency in our API we enforce the preconditions here.
float length = getLength();
if (startD < 0) {
diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java
index 29d82fa..5750954 100644
--- a/graphics/java/android/graphics/Rasterizer.java
+++ b/graphics/java/android/graphics/Rasterizer.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkRasterizer.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 635e78e..cc5b3b9 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -40,7 +40,7 @@
/** @hide */
private static int getPanelFrameSize() {
- final int DefaultSize = 100 * 1024 * 1024; // 100 MB;
+ final int DefaultSize = 150 * 1024 * 1024; // 150 MB;
return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize),
DefaultSize);
}
@@ -262,7 +262,7 @@
protected void throwIfCannotDraw(Bitmap bitmap) {
super.throwIfCannotDraw(bitmap);
int bitmapSize = bitmap.getByteCount();
- if (bitmapSize > MAX_BITMAP_SIZE) {
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE && bitmapSize > MAX_BITMAP_SIZE) {
throw new RuntimeException(
"Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
}
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 411a10b..5211e3a 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -165,7 +165,7 @@
public String toShortString() {
return toShortString(new StringBuilder(32));
}
-
+
/**
* Return a string representation of the rectangle in a compact form.
* @hide
@@ -184,7 +184,7 @@
*
* <p>You can later recover the Rect from this string through
* {@link #unflattenFromString(String)}.
- *
+ *
* @return Returns a new String of the form "left top right bottom"
*/
@NonNull
@@ -314,7 +314,7 @@
public final int height() {
return bottom - top;
}
-
+
/**
* @return the horizontal center of the rectangle. If the computed value
* is fractional, this method returns the largest integer that is
@@ -323,7 +323,7 @@
public final int centerX() {
return (left + right) >> 1;
}
-
+
/**
* @return the vertical center of the rectangle. If the computed value
* is fractional, this method returns the largest integer that is
@@ -332,14 +332,14 @@
public final int centerY() {
return (top + bottom) >> 1;
}
-
+
/**
* @return the exact horizontal center of the rectangle as a float.
*/
public final float exactCenterX() {
return (left + right) * 0.5f;
}
-
+
/**
* @return the exact vertical center of the rectangle as a float.
*/
@@ -493,7 +493,7 @@
* @param top The top of the rectangle being tested for containment
* @param right The right side of the rectangle being tested for containment
* @param bottom The bottom of the rectangle being tested for containment
- * @return true iff the the 4 specified sides of a rectangle are inside or
+ * @return true iff the 4 specified sides of a rectangle are inside or
* equal to this rectangle
*/
public boolean contains(int left, int top, int right, int bottom) {
@@ -548,7 +548,7 @@
}
return false;
}
-
+
/**
* If the specified rectangle intersects this rectangle, return true and set
* this rectangle to that intersection, otherwise return false and do not
@@ -670,7 +670,7 @@
public void union(@NonNull Rect r) {
union(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* Update this Rect to enclose itself and the [x,y] coordinate. There is no
* check to see that this rectangle is non-empty.
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index ff50a0c..5b9b764 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -38,7 +38,7 @@
public float top;
public float right;
public float bottom;
-
+
/**
* Create a new empty RectF. All coordinates are initialized to 0.
*/
@@ -78,7 +78,7 @@
bottom = r.bottom;
}
}
-
+
public RectF(@Nullable Rect r) {
if (r == null) {
left = top = right = bottom = 0.0f;
@@ -121,7 +121,7 @@
public String toShortString() {
return toShortString(new StringBuilder(32));
}
-
+
/**
* Return a string representation of the rectangle in a compact form.
* @hide
@@ -134,7 +134,7 @@
sb.append(','); sb.append(bottom); sb.append(']');
return sb.toString();
}
-
+
/**
* Print short representation to given writer.
* @hide
@@ -183,14 +183,14 @@
public final float centerY() {
return (top + bottom) * 0.5f;
}
-
+
/**
* Set the rectangle to (0,0,0,0)
*/
public void setEmpty() {
left = right = top = bottom = 0;
}
-
+
/**
* Set the rectangle's coordinates to the specified values. Note: no range
* checking is performed, so it is up to the caller to ensure that
@@ -220,7 +220,7 @@
this.right = src.right;
this.bottom = src.bottom;
}
-
+
/**
* Copy the coordinates from src into this rectangle.
*
@@ -261,7 +261,7 @@
left = newLeft;
top = newTop;
}
-
+
/**
* Inset the rectangle by (dx,dy). If dx is positive, then the sides are
* moved inwards, making the rectangle narrower. If dx is negative, then the
@@ -293,7 +293,7 @@
return left < right && top < bottom // check for empty first
&& x >= left && x < right && y >= top && y < bottom;
}
-
+
/**
* Returns true iff the 4 specified sides of a rectangle are inside or equal
* to this rectangle. i.e. is this rectangle a superset of the specified
@@ -303,7 +303,7 @@
* @param top The top of the rectangle being tested for containment
* @param right The right side of the rectangle being tested for containment
* @param bottom The bottom of the rectangle being tested for containment
- * @return true iff the the 4 specified sides of a rectangle are inside or
+ * @return true iff the 4 specified sides of a rectangle are inside or
* equal to this rectangle
*/
public boolean contains(float left, float top, float right, float bottom) {
@@ -313,7 +313,7 @@
&& this.left <= left && this.top <= top
&& this.right >= right && this.bottom >= bottom;
}
-
+
/**
* Returns true iff the specified rectangle r is inside or equal to this
* rectangle. An empty rectangle never contains another rectangle.
@@ -329,7 +329,7 @@
&& left <= r.left && top <= r.top
&& right >= r.right && bottom >= r.bottom;
}
-
+
/**
* If the rectangle specified by left,top,right,bottom intersects this
* rectangle, return true and set this rectangle to that intersection,
@@ -367,7 +367,7 @@
}
return false;
}
-
+
/**
* If the specified rectangle intersects this rectangle, return true and set
* this rectangle to that intersection, otherwise return false and do not
@@ -382,7 +382,7 @@
public boolean intersect(@NonNull RectF r) {
return intersect(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* If rectangles a and b intersect, return true and set this rectangle to
* that intersection, otherwise return false and do not change this
@@ -406,7 +406,7 @@
}
return false;
}
-
+
/**
* Returns true if this rectangle intersects the specified rectangle.
* In no event is this rectangle modified. No check is performed to see
@@ -426,7 +426,7 @@
return this.left < right && left < this.right
&& this.top < bottom && top < this.bottom;
}
-
+
/**
* Returns true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified. To record the intersection,
@@ -441,7 +441,7 @@
return a.left < b.right && b.left < a.right
&& a.top < b.bottom && b.top < a.bottom;
}
-
+
/**
* Set the dst integer Rect by rounding this rectangle's coordinates
* to their nearest integer values.
@@ -489,7 +489,7 @@
}
}
}
-
+
/**
* Update this Rect to enclose itself and the specified rectangle. If the
* specified rectangle is empty, nothing is done. If this rectangle is empty
@@ -500,7 +500,7 @@
public void union(@NonNull RectF r) {
union(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* Update this Rect to enclose itself and the [x,y] coordinate. There is no
* check to see that this rectangle is non-empty.
@@ -520,7 +520,7 @@
bottom = y;
}
}
-
+
/**
* Swap top/bottom or left/right if there are flipped (i.e. left > right
* and/or top > bottom). This can be called if
@@ -548,7 +548,7 @@
public int describeContents() {
return 0;
}
-
+
/**
* Write this rectangle to the specified parcel. To restore a rectangle from
* a parcel, use readFromParcel()
@@ -561,7 +561,7 @@
out.writeFloat(right);
out.writeFloat(bottom);
}
-
+
public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
/**
* Return a new rectangle from the data in the specified parcel.
@@ -572,7 +572,7 @@
r.readFromParcel(in);
return r;
}
-
+
/**
* Return an array of rectangles of the specified size.
*/
@@ -581,7 +581,7 @@
return new RectF[size];
}
};
-
+
/**
* Set the rectangle's coordinates from the data stored in the specified
* parcel. To write a rectangle to a parcel, call writeToParcel().
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 2732569..0650b78 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -765,12 +765,12 @@
* Default value is false. See
* {@link #setProjectBackwards(boolean)} for a description of what this entails.
*
- * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise.
+ * @param shouldReceive True if this RenderNode is a projection receiver, false otherwise.
* Default is false.
* @return True if the value changed, false if the new value was the same as the previous value.
*/
- public boolean setProjectionReceiver(boolean shouldRecieve) {
- return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+ public boolean setProjectionReceiver(boolean shouldReceive) {
+ return nSetProjectionReceiver(mNativeRenderNode, shouldReceive);
}
/**
@@ -1799,7 +1799,7 @@
private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
@CriticalNative
- private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+ private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive);
@CriticalNative
private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 50b167e..3256f31 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -318,7 +318,7 @@
}
/**
- * Releases the the texture content. This is needed in single buffered mode to allow the image
+ * Releases the texture content. This is needed in single buffered mode to allow the image
* content producer to take ownership of the image buffer.
* <p>
* For more information see {@link #SurfaceTexture(int, boolean)}.
@@ -431,7 +431,7 @@
* error.
* <p>
* Note that while calling this method causes all the buffers to be freed
- * from the perspective of the the SurfaceTexture, if there are additional
+ * from the perspective of the SurfaceTexture, if there are additional
* references on the buffers (e.g. if a buffer is referenced by a client or
* by OpenGL ES as a texture) then those buffer will remain allocated.
* <p>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 4c4e8fa..fd78816 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -600,7 +600,7 @@
* {@link #setWeight} and {@link #setItalic}.
*
* If {@link #setWeight} is not called, the fallback family keeps the default weight.
- * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
+ * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default
* italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
* is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
* terms of fallback. The default weight and italic information are overridden by calling
@@ -794,7 +794,7 @@
/**
* Returns the maximum capacity of custom fallback families.
*
- * This includes the the first font family passed to the constructor.
+ * This includes the first font family passed to the constructor.
* It is guaranteed that the value will be greater than or equal to 64.
*
* @return the maximum number of font families for the custom fallback
@@ -816,7 +816,7 @@
/**
* Sets a system fallback by name.
*
- * You can specify generic font familiy names or OEM specific family names. If the system
+ * You can specify generic font family names or OEM specific family names. If the system
* don't have a specified fallback, the default fallback is used instead.
* For more information about generic font families, see <a
* href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 81769e2..6bb22a1 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkXfermode.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
@@ -28,7 +28,7 @@
* Xfermode is the base class for objects that are called to implement custom
* "transfer-modes" in the drawing pipeline. The static function Create(Modes)
* can be called to return an instance of any of the predefined subclasses as
- * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
+ * specified in the Modes enum. When an Xfermode is assigned to a Paint, then
* objects drawn with that paint have the xfermode applied.
*/
public class Xfermode {
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index ce35b55..b0c7f20 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -63,7 +63,7 @@
private int mWidth;
/**
- * The height of the the image.
+ * The height of the image.
*/
private int mHeight;
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 688425a..7ee7d6b 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -98,7 +98,7 @@
* extra content to reveal within the clip path when performing affine transformations on the
* layers.
*
- * Each layers will reserve 25% of it's width and height.
+ * Each layers will reserve 25% of its width and height.
*
* As a result, the view port of the layers is smaller than their intrinsic width and height.
*/
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 4972e92..7f2feac 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -839,7 +839,7 @@
}
/**
- * Describes the current state, as a union of primitve states, such as
+ * Describes the current state, as a union of primitive states, such as
* {@link android.R.attr#state_focused},
* {@link android.R.attr#state_selected}, etc.
* Some drawables may modify their imagery based on the selected state.
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 166a795..29d033e 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -936,7 +936,7 @@
}
/**
- * Retrn the inner radius of the ring
+ * Return the inner radius of the ring
*
* @see #setInnerRadius(int)
* @attr ref android.R.styleable#GradientDrawable_innerRadius
diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java
index 393fdee..299f6d5 100644
--- a/graphics/java/android/graphics/drawable/shapes/PathShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java
@@ -93,7 +93,7 @@
&& Float.compare(pathShape.mStdHeight, mStdHeight) == 0
&& Float.compare(pathShape.mScaleX, mScaleX) == 0
&& Float.compare(pathShape.mScaleY, mScaleY) == 0
- // Path does not have equals implementation but incase it gains one, use it here
+ // Path does not have equals implementation but in case it gains one, use it here
&& Objects.equals(mPath, pathShape.mPath);
}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 318aadd..2893177 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -789,7 +789,7 @@
return false;
}
- // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since
+ // ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since
// underlying native font object holds buffer address, check if this buffer points exactly
// the same address as a shortcut of equality. For being compatible with of API30 or before,
// check buffer position even if the buffer points the same address.
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index ff38282..abcafb6 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -34,7 +34,7 @@
*/
public class FontFileUtil {
- private FontFileUtil() {} // Do not instanciate
+ private FontFileUtil() {} // Do not instantiate
/**
* Unpack the weight value from packed integer.
@@ -87,7 +87,7 @@
}
if (weight != -1 && italic != -1) {
- // Both weight/italic style are specifeid by variation settings.
+ // Both weight/italic style are specified by variation settings.
// No need to look into OS/2 table.
// TODO: Good to look HVAR table to check if this font supports wght/ital axes.
return pack(weight, italic == 1);
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a90961e..f727f5b 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -216,7 +216,7 @@
} else if (defaultFamily != null) {
familyListSet.familyList.add(defaultFamily);
} else {
- // There is no valid for for default fallback. Ignore.
+ // There is no valid for default fallback. Ignore.
}
}
}
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 0c6d4bd..d8cf21e 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -376,8 +376,8 @@
* @see LineBreaker#computeLineBreaks
*/
public static class Result {
- // Following two contstant must be synced with minikin's line breaker.
- // TODO(nona): Remove these constatns by introducing native methods.
+ // Following two constants must be synced with minikin's line breaker.
+ // TODO(nona): Remove these constants by introducing native methods.
private static final int TAB_MASK = 0x20000000;
private static final int HYPHEN_MASK = 0xFF;
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index f8328b1..671eb6e 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -139,7 +139,7 @@
* Returns the glyph ID used for drawing the glyph at the given index.
*
* @param index the glyph index
- * @return An glyph ID of the font.
+ * @return A glyph ID of the font.
*/
@IntRange(from = 0)
public int getGlyphId(@IntRange(from = 0) int index) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index e93b0bf..1fbaeea 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -256,8 +256,10 @@
static Color getContainerBackgroundColor(
@NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
final Activity activity = container.getTopNonFinishingActivity();
- if (activity == null || !activity.isResumed()) {
- // This can happen when the top activity in the container is from a different process.
+ if (activity == null) {
+ // This can happen when the activities in the container are from a different process.
+ // TODO(b/340984203) Report whether the top activity is in the same process. Use default
+ // color if not.
return defaultColor;
}
@@ -515,8 +517,11 @@
private void onStartDragging() {
mRenderer.mIsDragging = true;
mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ mRenderer.updateSurface();
+
+ // Veil visibility change should be applied together with the surface boost transaction in
+ // the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.updateSurface(t);
mRenderer.showVeils(t);
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
@@ -532,18 +537,18 @@
@GuardedBy("mLock")
private void onDrag() {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.updateSurface(t);
- t.apply();
+ mRenderer.updateSurface();
}
@GuardedBy("mLock")
private void onFinishDragging() {
mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
mRenderer.setDividerPosition(mDividerPosition);
+ mRenderer.updateSurface();
+ // Veil visibility change should be applied together with the surface boost transaction in
+ // the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.updateSurface(t);
mRenderer.hideVeils(t);
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
@@ -994,6 +999,22 @@
* Updates the positions and crops of the divider surface and veil surfaces. This method
* should be called when {@link #mProperties} is changed or while dragging to update the
* position of the divider surface and the veil surfaces.
+ *
+ * This method applies the changes in a stand-alone surface transaction immediately.
+ */
+ private void updateSurface() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ updateSurface(t);
+ t.apply();
+ }
+
+ /**
+ * Updates the positions and crops of the divider surface and veil surfaces. This method
+ * should be called when {@link #mProperties} is changed or while dragging to update the
+ * position of the divider surface and the veil surfaces.
+ *
+ * This method applies the changes in the provided surface transaction and can be synced
+ * with other changes.
*/
private void updateSurface(@NonNull SurfaceControl.Transaction t) {
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index a23a4741..f9a6caf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -358,6 +359,13 @@
wct.addTaskFragmentOperation(fragmentToken, operation);
}
+ void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, boolean pinned) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_PINNED).setBooleanValue(pinned).build();
+ wct.addTaskFragmentOperation(fragmentToken, operation);
+ }
+
void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, boolean dimOnTask) {
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
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 5b0e6b9..13c2d1f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -350,8 +350,7 @@
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
- false /* isolated */);
+ mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -1078,8 +1077,7 @@
return true;
}
- // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
- if (container != null && container.isIsolatedNavigationEnabled()) {
+ if (container != null && container.shouldSkipActivityResolving()) {
return true;
}
@@ -1535,8 +1533,7 @@
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
if (taskFragmentContainer != null
- && taskFragmentContainer.isIsolatedNavigationEnabled()) {
- // Skip resolving if started from an isolated navigated TaskFragmentContainer.
+ && taskFragmentContainer.shouldSkipActivityResolving()) {
return null;
}
if (isAssociatedWithOverlay(launchingActivity)) {
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 0e4fb30..2704813 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -401,18 +401,26 @@
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
+ setTaskFragmentPinned(wct, secondaryContainer, !isStacked /* pinned */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
- * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}.
+ * <p>
+ * If a container enables isolated navigation, activities can't be launched to this container
+ * unless explicitly requested to be launched to.
+ *
+ * @see TaskFragmentContainer#isOverlayWithActivityAssociation()
*/
void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
boolean isolatedNavigationEnabled) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) {
+ return;
+ }
if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
return;
}
@@ -422,6 +430,28 @@
}
/**
+ * Sets whether to pin this {@link TaskFragmentContainer}.
+ * <p>
+ * If a container is pinned, it won't be chosen as the launch target unless it's the launching
+ * container.
+ *
+ * @see TaskFragmentContainer#isAlwaysOnTopOverlay()
+ * @see TaskContainer#getSplitPinContainer()
+ */
+ void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ boolean pinned) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) {
+ return;
+ }
+ if (container.isPinned() == pinned) {
+ return;
+ }
+ container.setPinned(pinned);
+ setTaskFragmentPinned(wct, container.getTaskFragmentToken(), pinned);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
@@ -586,6 +616,11 @@
super.setCompanionTaskFragment(wct, primary, secondary);
}
+ /**
+ * Applies the {@code attributes} to a standalone {@code container}.
+ *
+ * @param minDimensions the minimum dimension of the container.
+ */
void applyActivityStackAttributes(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@@ -594,16 +629,17 @@
final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
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.isAlwaysOnTopOverlay() && !isFillParent;
final boolean dimOnTask = !isFillParent
- && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
- && Flags.fullscreenDimFlag();
+ && Flags.fullscreenDimFlag()
+ && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
final IBinder fragmentToken = container.getTaskFragmentToken();
+ if (container.isAlwaysOnTopOverlay()) {
+ setTaskFragmentPinned(wct, container, !isFillParent);
+ } else if (container.isOverlayWithActivityAssociation()) {
+ setTaskFragmentIsolatedNavigation(wct, container, !isFillParent);
+ }
+
// TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
// and WCT#setWindowingMode to take fragmentToken.
resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
@@ -612,7 +648,6 @@
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
// Always use default animation for standalone ActivityStack.
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
- setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
}
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 c952dfe..4825543 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -184,6 +184,11 @@
private boolean mIsIsolatedNavigationEnabled;
/**
+ * Whether this TaskFragment is pinned.
+ */
+ private boolean mIsPinned;
+
+ /**
* Whether to apply dimming on the parent Task that was requested last.
*/
private boolean mLastDimOnTask;
@@ -893,6 +898,34 @@
mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
}
+ /**
+ * Returns whether this container is pinned.
+ *
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
+ */
+ boolean isPinned() {
+ return mIsPinned;
+ }
+
+ /**
+ * Sets whether to pin this container or not.
+ *
+ * @see #isPinned()
+ */
+ void setPinned(boolean pinned) {
+ mIsPinned = pinned;
+ }
+
+ /**
+ * Indicates to skip activity resolving if the activity is from this container.
+ *
+ * @see #isIsolatedNavigationEnabled()
+ * @see #isPinned()
+ */
+ boolean shouldSkipActivityResolving() {
+ return isIsolatedNavigationEnabled() || isPinned();
+ }
+
/** Sets whether to apply dim on the parent Task. */
void setLastDimOnTask(boolean lastDimOnTask) {
mLastDimOnTask = lastDimOnTask;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index ad913c9..b0a45e2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -631,15 +631,8 @@
assertEquals(defaultColor,
DividerPresenter.getContainerBackgroundColor(container, defaultColor));
- // When the top non-finishing activity is not resumed, the default color should be returned.
+ // When the top non-finishing activity is non-null, its background color should be returned.
when(container.getTopNonFinishingActivity()).thenReturn(activity);
- when(activity.isResumed()).thenReturn(false);
- assertEquals(defaultColor,
- DividerPresenter.getContainerBackgroundColor(container, defaultColor));
-
- // When the top non-finishing activity is resumed, its background color should be returned.
- when(container.getTopNonFinishingActivity()).thenReturn(activity);
- when(activity.isResumed()).thenReturn(true);
assertEquals(activityBackgroundColor,
DividerPresenter.getContainerBackgroundColor(container, defaultColor));
}
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 049a9e2..9ebcb759 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
@@ -188,6 +188,32 @@
}
@Test
+ public void testSetIsolatedNavigation_overlayFeatureDisabled_earlyReturn() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+
+ mSplitPresenter.setTaskFragmentIsolatedNavigation(mTransaction, container,
+ !container.isIsolatedNavigationEnabled());
+
+ verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+ any(IBinder.class), anyBoolean());
+ }
+
+ @Test
+ public void testSetPinned_overlayFeatureDisabled_earlyReturn() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+
+ mSplitPresenter.setTaskFragmentPinned(mTransaction, container,
+ !container.isPinned());
+
+ verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(IBinder.class),
+ anyBoolean());
+ }
+
+ @Test
public void testGetAllNonFinishingOverlayContainers() {
assertThat(mSplitController.getAllNonFinishingOverlayContainers()).isEmpty();
@@ -608,8 +634,11 @@
WINDOWING_MODE_UNDEFINED);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+ any(TaskFragmentContainer.class), anyBoolean());
+ verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+ any(TaskFragmentContainer.class), anyBoolean());
}
@Test
@@ -630,9 +659,9 @@
WINDOWING_MODE_MULTI_WINDOW);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
- // Set isolated navigation to false if the overlay container is associated with
- // the launching activity.
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+ any(TaskFragmentContainer.class), anyBoolean());
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
}
@@ -655,10 +684,9 @@
container, WINDOWING_MODE_MULTI_WINDOW);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
- // Set isolated navigation to false if the overlay container is associated with
- // the launching activity.
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
- container, true);
+ verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+ any(TaskFragmentContainer.class), anyBoolean());
+ verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true);
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
}
@@ -678,6 +706,8 @@
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+ any(TaskFragmentContainer.class), anyBoolean());
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index c677484..3fbce9ec 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -285,6 +286,28 @@
}
@Test
+ public void testSetTaskFragmentPinned() {
+ final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+ // Verify the default.
+ assertFalse(container.isPinned());
+
+ mPresenter.setTaskFragmentPinned(mTransaction, container, true);
+
+ final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_PINNED).setBooleanValue(true).build();
+ verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(),
+ expectedOperation);
+ assertTrue(container.isPinned());
+
+ // No request to set the same animation params.
+ clearInvocations(mTransaction);
+ mPresenter.setTaskFragmentPinned(mTransaction, container, true);
+
+ verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
+ }
+
+ @Test
public void testGetMinDimensionsForIntent() {
final Intent intent = new Intent(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class);
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index fe68123..8977d5c 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -71,3 +71,10 @@
description: "Hides the bubble overflow if there aren't any overflowed bubbles"
bug: "334175587"
}
+
+flag {
+ name: "enable_retrievable_bubbles"
+ namespace: "multitasking"
+ description: "Allow opening bubbles overflow UI without bubbles being visible"
+ bug: "340337839"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
similarity index 88%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
index b41454d..bdd89c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.desktopmode;
+package com.android.wm.shell.shared;
import android.annotation.NonNull;
import android.content.Context;
@@ -41,15 +41,6 @@
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_change_display", false);
-
- /**
- * Flag to indicate that desktop stashing is enabled.
- * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
- * will be added to the desktop.
- */
- private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_stashing", false);
-
/**
* Flag to indicate whether to apply shadows to windows in desktop mode.
*/
@@ -109,14 +100,6 @@
}
/**
- * Return {@code true} if desktop task stashing is enabled when going home.
- * Allows users to use home screen to add tasks to desktop.
- */
- public static boolean isStashingEnabled() {
- return IS_STASHING_ENABLED;
- }
-
- /**
* Return whether to use window shadows.
*
* @param isFocusedWindow whether the window to apply shadows to is focused
@@ -144,7 +127,7 @@
/**
* Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
*/
- static int getMaxTaskLimit() {
+ public static int getMaxTaskLimit() {
return MAX_TASK_LIMIT;
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dcd4062..785e30d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -69,8 +69,12 @@
/** Returns {@code true} if the transition is opening or closing mode. */
public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
- return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
- || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+ return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+ }
+
+ /** Returns {@code true} if the transition is opening mode. */
+ public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) {
+ return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
}
/** Returns {@code true} if the transition has a display change. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 163a896..5600664 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -646,7 +646,7 @@
private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (!mOnBackStartDispatched) {
- Log.e(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
+ Log.d(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
return;
}
if (callback == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 037b1ec..c988c2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.wm.shell.back
import android.animation.Animator
@@ -34,6 +35,7 @@
import android.view.SurfaceControl
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
+import android.view.animation.Transformation
import android.window.BackEvent
import android.window.BackMotionEvent
import android.window.BackNavigationInfo
@@ -46,52 +48,45 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
-/** Class that defines cross-activity animation. */
-@ShellMainThread
-class CrossActivityBackAnimation @Inject constructor(
+abstract class CrossActivityBackAnimation(
private val context: Context,
private val background: BackAnimationBackground,
- private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ protected val transaction: SurfaceControl.Transaction,
+ private val choreographer: Choreographer
) : ShellBackAnimation() {
- private val startClosingRect = RectF()
- private val targetClosingRect = RectF()
- private val currentClosingRect = RectF()
+ protected val startClosingRect = RectF()
+ protected val targetClosingRect = RectF()
+ protected val currentClosingRect = RectF()
- private val startEnteringRect = RectF()
- private val targetEnteringRect = RectF()
- private val currentEnteringRect = RectF()
+ protected val startEnteringRect = RectF()
+ protected val targetEnteringRect = RectF()
+ protected val currentEnteringRect = RectF()
- private val backAnimRect = Rect()
+ protected val backAnimRect = Rect()
private val cropRect = Rect()
private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
- private val backAnimationRunner = BackAnimationRunner(
- Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY
- )
+ private val backAnimationRunner =
+ BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
private val initialTouchPos = PointF()
private val transformMatrix = Matrix()
private val tmpFloat9 = FloatArray(9)
- private var enteringTarget: RemoteAnimationTarget? = null
- private var closingTarget: RemoteAnimationTarget? = null
- private val transaction = SurfaceControl.Transaction()
+ protected var enteringTarget: RemoteAnimationTarget? = null
+ protected var closingTarget: RemoteAnimationTarget? = null
private var triggerBack = false
private var finishCallback: IRemoteAnimationFinishedCallback? = null
private val progressAnimator = BackProgressAnimator()
private val displayBoundsMargin =
context.resources.getDimension(R.dimen.cross_task_back_vertical_margin)
- private val enteringStartOffset =
- context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
private val gestureInterpolator = Interpolators.BACK_GESTURE
- private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator()
private var scrimLayer: SurfaceControl? = null
@@ -103,13 +98,42 @@
private var rightLetterboxLayer: SurfaceControl? = null
private var letterboxColor: Int = 0
+ /** Background color to be used during the animation, also see [getBackgroundColor] */
+ protected var customizedBackgroundColor = 0
+
+ /**
+ * Whether the entering target should be shifted vertically with the user gesture in pre-commit
+ */
+ abstract val allowEnteringYShift: Boolean
+
+ /**
+ * Subclasses must set the [startEnteringRect] and [targetEnteringRect] to define the movement
+ * of the enteringTarget during pre-commit phase.
+ */
+ abstract fun preparePreCommitEnteringRectMovement()
+
+ /**
+ * Returns a base transformation to apply to the entering target during pre-commit. The system
+ * will apply the default animation on top of it.
+ */
+ protected open fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation? =
+ null
+
override fun onConfigurationChanged(newConfiguration: Configuration) {
cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
}
override fun getRunner() = backAnimationRunner
- private fun startBackAnimation(backMotionEvent: BackMotionEvent) {
+ private fun getBackgroundColor(): Int =
+ when {
+ customizedBackgroundColor != 0 -> customizedBackgroundColor
+ isLetterboxed -> letterboxColor
+ enteringTarget != null -> enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
+ else -> 0
+ }
+
+ protected open fun startBackAnimation(backMotionEvent: BackMotionEvent) {
if (enteringTarget == null || closingTarget == null) {
ProtoLog.d(
ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
@@ -122,8 +146,8 @@
transaction.setAnimationTransaction()
isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
- enteringHasSameLetterbox = isLetterboxed &&
- closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
+ enteringHasSameLetterbox =
+ isLetterboxed && closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
if (isLetterboxed && !enteringHasSameLetterbox) {
// Play animation with letterboxes, if closing and entering target have mismatching
@@ -143,32 +167,27 @@
targetClosingRect.scaleCentered(MAX_SCALE)
if (backMotionEvent.swipeEdge != BackEvent.EDGE_RIGHT) {
targetClosingRect.offset(
- startClosingRect.right - targetClosingRect.right - displayBoundsMargin, 0f
+ startClosingRect.right - targetClosingRect.right - displayBoundsMargin,
+ 0f
)
}
- // the entering target starts 96dp to the left of the screen edge...
- startEnteringRect.set(startClosingRect)
- startEnteringRect.offset(-enteringStartOffset, 0f)
+ preparePreCommitEnteringRectMovement()
- // ...and gets scaled in sync with the closing target
- targetEnteringRect.set(startEnteringRect)
- targetEnteringRect.scaleCentered(MAX_SCALE)
-
- // Draw background with task background color (or letterbox color).
- val backgroundColor = if (isLetterboxed) {
- letterboxColor
- } else {
- enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
- }
background.ensureBackground(
- closingTarget!!.windowConfiguration.bounds, backgroundColor, transaction
+ closingTarget!!.windowConfiguration.bounds,
+ getBackgroundColor(),
+ transaction
)
ensureScrimLayer()
if (isLetterboxed && enteringHasSameLetterbox) {
// crop left and right letterboxes
- cropRect.set(closingTarget!!.localBounds.left, 0, closingTarget!!.localBounds.right,
- closingTarget!!.windowConfiguration.bounds.height())
+ cropRect.set(
+ closingTarget!!.localBounds.left,
+ 0,
+ closingTarget!!.localBounds.right,
+ closingTarget!!.windowConfiguration.bounds.height()
+ )
// and add fake letterbox square surfaces instead
ensureLetterboxes()
} else {
@@ -185,8 +204,14 @@
currentClosingRect.offset(0f, yOffset)
applyTransform(closingTarget?.leash, currentClosingRect, 1f)
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
- currentEnteringRect.offset(0f, yOffset)
- applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ if (allowEnteringYShift) currentEnteringRect.offset(0f, yOffset)
+ val enteringTransformation = getPreCommitEnteringBaseTransformation(progress)
+ applyTransform(
+ enteringTarget?.leash,
+ currentEnteringRect,
+ enteringTransformation?.alpha ?: 1f,
+ enteringTransformation
+ )
applyTransaction()
}
@@ -199,30 +224,25 @@
val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f)
val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio)
// limit y-shift so surface never passes 8dp screen margin
- val deltaY = yDirection * interpolatedYRatio * max(
- 0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin
- )
+ val deltaY =
+ max(0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin) *
+ interpolatedYRatio *
+ yDirection
return deltaY
}
- private fun onGestureCommitted() {
- if (closingTarget?.leash == null || enteringTarget?.leash == null ||
- !enteringTarget!!.leash.isValid || !closingTarget!!.leash.isValid
+ protected open fun onGestureCommitted() {
+ if (
+ closingTarget?.leash == null ||
+ enteringTarget?.leash == null ||
+ !enteringTarget!!.leash.isValid ||
+ !closingTarget!!.leash.isValid
) {
finishAnimation()
return
}
- // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
- // coordinate of the gesture driven phase. Let's update the start and target rects and kick
- // off the animator
- startClosingRect.set(currentClosingRect)
- startEnteringRect.set(currentEnteringRect)
- targetEnteringRect.set(backAnimRect)
- targetClosingRect.set(backAnimRect)
- targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
-
- val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION)
+ val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_COMMIT_DURATION)
valueAnimator.addUpdateListener { animation: ValueAnimator ->
val progress = animation.animatedFraction
onPostCommitProgress(progress)
@@ -230,27 +250,22 @@
background.resetStatusBarCustomization()
}
}
- valueAnimator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- background.resetStatusBarCustomization()
- finishAnimation()
+ valueAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ background.resetStatusBarCustomization()
+ finishAnimation()
+ }
}
- })
+ )
valueAnimator.start()
}
- private fun onPostCommitProgress(linearProgress: Float) {
- val closingAlpha = max(1f - linearProgress * 2, 0f)
- val progress = postCommitInterpolator.getInterpolation(linearProgress)
+ protected open fun onPostCommitProgress(linearProgress: Float) {
scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) }
- currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
- applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
- currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
- applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
- applyTransaction()
}
- private fun finishAnimation() {
+ protected open fun finishAnimation() {
enteringTarget?.let {
if (it.leash != null && it.leash.isValid) {
transaction.setCornerRadius(it.leash, 0f)
@@ -278,47 +293,56 @@
enteringHasSameLetterbox = false
}
- private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
+ protected fun applyTransform(
+ leash: SurfaceControl?,
+ rect: RectF,
+ alpha: Float,
+ baseTransformation: Transformation? = null
+ ) {
if (leash == null || !leash.isValid) return
val scale = rect.width() / backAnimRect.width()
- transformMatrix.reset()
- val scalePivotX = if (isLetterboxed && enteringHasSameLetterbox) {
- closingTarget!!.localBounds.left.toFloat()
- } else {
- 0f
- }
- transformMatrix.setScale(scale, scale, scalePivotX, 0f)
- transformMatrix.postTranslate(rect.left, rect.top)
- transaction.setAlpha(leash, alpha)
- .setMatrix(leash, transformMatrix, tmpFloat9)
+ val matrix = baseTransformation?.matrix ?: transformMatrix.apply { reset() }
+ val scalePivotX =
+ if (isLetterboxed && enteringHasSameLetterbox) {
+ closingTarget!!.localBounds.left.toFloat()
+ } else {
+ 0f
+ }
+ matrix.postScale(scale, scale, scalePivotX, 0f)
+ matrix.postTranslate(rect.left, rect.top)
+ transaction
+ .setAlpha(leash, keepMinimumAlpha(alpha))
+ .setMatrix(leash, matrix, tmpFloat9)
.setCrop(leash, cropRect)
.setCornerRadius(leash, cornerRadius)
}
- private fun applyTransaction() {
- transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
+ protected fun applyTransaction() {
+ transaction.setFrameTimelineVsync(choreographer.vsyncId)
transaction.apply()
}
private fun ensureScrimLayer() {
if (scrimLayer != null) return
val isDarkTheme: Boolean = isDarkMode(context)
- val scrimBuilder = SurfaceControl.Builder()
- .setName("Cross-Activity back animation scrim")
- .setCallsite("CrossActivityBackAnimation")
- .setColorLayer()
- .setOpaque(false)
- .setHidden(false)
+ val scrimBuilder =
+ SurfaceControl.Builder()
+ .setName("Cross-Activity back animation scrim")
+ .setCallsite("CrossActivityBackAnimation")
+ .setColorLayer()
+ .setOpaque(false)
+ .setHidden(false)
rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder)
scrimLayer = scrimBuilder.build()
val colorComponents = floatArrayOf(0f, 0f, 0f)
maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT
- val scrimCrop = if (isLetterboxed) {
- closingTarget!!.windowConfiguration.bounds
- } else {
- closingTarget!!.localBounds
- }
+ val scrimCrop =
+ if (isLetterboxed) {
+ closingTarget!!.windowConfiguration.bounds
+ } else {
+ closingTarget!!.localBounds
+ }
transaction
.setColor(scrimLayer, colorComponents)
.setAlpha(scrimLayer!!, maxScrimAlpha)
@@ -339,21 +363,34 @@
private fun ensureLetterboxes() {
closingTarget?.let { t ->
if (t.localBounds.left != 0 && leftLetterboxLayer == null) {
- val bounds = Rect(0, t.windowConfiguration.bounds.top, t.localBounds.left,
- t.windowConfiguration.bounds.bottom)
+ val bounds =
+ Rect(
+ 0,
+ t.windowConfiguration.bounds.top,
+ t.localBounds.left,
+ t.windowConfiguration.bounds.bottom
+ )
leftLetterboxLayer = ensureLetterbox(bounds)
}
- if (t.localBounds.right != t.windowConfiguration.bounds.right &&
- rightLetterboxLayer == null) {
- val bounds = Rect(t.localBounds.right, t.windowConfiguration.bounds.top,
- t.windowConfiguration.bounds.right, t.windowConfiguration.bounds.bottom)
+ if (
+ t.localBounds.right != t.windowConfiguration.bounds.right &&
+ rightLetterboxLayer == null
+ ) {
+ val bounds =
+ Rect(
+ t.localBounds.right,
+ t.windowConfiguration.bounds.top,
+ t.windowConfiguration.bounds.right,
+ t.windowConfiguration.bounds.bottom
+ )
rightLetterboxLayer = ensureLetterbox(bounds)
}
}
}
private fun ensureLetterbox(bounds: Rect): SurfaceControl {
- val letterboxBuilder = SurfaceControl.Builder()
+ val letterboxBuilder =
+ SurfaceControl.Builder()
.setName("Cross-Activity back animation letterbox")
.setCallsite("CrossActivityBackAnimation")
.setColorLayer()
@@ -362,13 +399,17 @@
rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, letterboxBuilder)
val layer = letterboxBuilder.build()
- val colorComponents = floatArrayOf(Color.red(letterboxColor) / 255f,
- Color.green(letterboxColor) / 255f, Color.blue(letterboxColor) / 255f)
+ val colorComponents =
+ floatArrayOf(
+ Color.red(letterboxColor) / 255f,
+ Color.green(letterboxColor) / 255f,
+ Color.blue(letterboxColor) / 255f
+ )
transaction
- .setColor(layer, colorComponents)
- .setCrop(layer, bounds)
- .setRelativeLayer(layer, closingTarget!!.leash, 1)
- .show(layer)
+ .setColor(layer, colorComponents)
+ .setCrop(layer, bounds)
+ .setRelativeLayer(layer, closingTarget!!.leash, 1)
+ .show(layer)
return layer
}
@@ -389,8 +430,8 @@
}
override fun prepareNextAnimation(
- animationInfo: BackNavigationInfo.CustomAnimationInfo?,
- letterboxColor: Int
+ animationInfo: BackNavigationInfo.CustomAnimationInfo?,
+ letterboxColor: Int
): Boolean {
this.letterboxColor = letterboxColor
return false
@@ -415,9 +456,7 @@
}
override fun onBackCancelled() {
- progressAnimator.onBackCancelled {
- finishAnimation()
- }
+ progressAnimator.onBackCancelled { finishAnimation() }
}
override fun onBackInvoked() {
@@ -435,7 +474,8 @@
finishedCallback: IRemoteAnimationFinishedCallback
) {
ProtoLog.d(
- ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "Start back to activity animation."
+ ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
+ "Start back to activity animation."
)
for (a in apps) {
when (a.mode) {
@@ -452,23 +492,25 @@
}
companion object {
- /** Max scale of the entering/closing window.*/
- private const val MAX_SCALE = 0.9f
-
- /** Duration of post animation after gesture committed. */
- private const val POST_ANIMATION_DURATION = 300L
-
+ /** Max scale of the closing window. */
+ internal const val MAX_SCALE = 0.9f
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
+ private const val POST_COMMIT_DURATION = 300L
}
}
+// The target will loose focus when alpha == 0, so keep a minimum value for it.
+private fun keepMinimumAlpha(transAlpha: Float): Float {
+ return max(transAlpha.toDouble(), 0.005).toFloat()
+}
+
private fun isDarkMode(context: Context): Boolean {
return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
+ Configuration.UI_MODE_NIGHT_YES
}
-private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
+internal fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" }
left = start.left + (target.left - start.left) * progress
top = start.top + (target.top - start.top) * progress
@@ -476,7 +518,7 @@
bottom = start.bottom + (target.bottom - start.bottom) * progress
}
-private fun RectF.scaleCentered(
+internal fun RectF.scaleCentered(
scale: Float,
pivotX: Float = left + width() / 2,
pivotY: Float = top + height() / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
new file mode 100644
index 0000000..e6ec2b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -0,0 +1,256 @@
+/*
+ * 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.back
+
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.MathUtils
+import android.view.Choreographer
+import android.view.SurfaceControl
+import android.view.animation.Animation
+import android.view.animation.Transformation
+import android.window.BackMotionEvent
+import android.window.BackNavigationInfo
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import javax.inject.Inject
+
+/** Class that handles customized predictive cross activity back animations. */
+@ShellMainThread
+class CustomCrossActivityBackAnimation(
+ context: Context,
+ background: BackAnimationBackground,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ transaction: SurfaceControl.Transaction,
+ choreographer: Choreographer,
+ private val customAnimationLoader: CustomAnimationLoader
+) :
+ CrossActivityBackAnimation(
+ context,
+ background,
+ rootTaskDisplayAreaOrganizer,
+ transaction,
+ choreographer
+ ) {
+
+ private var enterAnimation: Animation? = null
+ private var closeAnimation: Animation? = null
+ private val transformation = Transformation()
+ private var gestureProgress = 0f
+
+ override val allowEnteringYShift = false
+
+ @Inject
+ constructor(
+ context: Context,
+ background: BackAnimationBackground,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ ) : this(
+ context,
+ background,
+ rootTaskDisplayAreaOrganizer,
+ SurfaceControl.Transaction(),
+ Choreographer.getInstance(),
+ CustomAnimationLoader(
+ TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
+ )
+ )
+
+ override fun preparePreCommitEnteringRectMovement() {
+ // No movement for the entering rect
+ startEnteringRect.set(startClosingRect)
+ targetEnteringRect.set(startClosingRect)
+ }
+
+ override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
+ gestureProgress = progress
+ transformation.clear()
+ enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
+ return transformation
+ }
+
+ override fun startBackAnimation(backMotionEvent: BackMotionEvent) {
+ super.startBackAnimation(backMotionEvent)
+ if (
+ closeAnimation == null ||
+ enterAnimation == null ||
+ closingTarget == null ||
+ enteringTarget == null
+ ) {
+ ProtoLog.d(
+ ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
+ "Enter animation or close animation is null."
+ )
+ return
+ }
+ initializeAnimation(closeAnimation!!, closingTarget!!.localBounds)
+ initializeAnimation(enterAnimation!!, enteringTarget!!.localBounds)
+ }
+
+ override fun onPostCommitProgress(linearProgress: Float) {
+ super.onPostCommitProgress(linearProgress)
+ if (closingTarget == null || enteringTarget == null) return
+
+ // TODO: Should we use the duration from the custom xml spec for the post-commit animation?
+ applyTransform(closingTarget!!.leash, currentClosingRect, linearProgress, closeAnimation!!)
+ val enteringProgress =
+ MathUtils.lerp(gestureProgress * PRE_COMMIT_MAX_PROGRESS, 1f, linearProgress)
+ applyTransform(
+ enteringTarget!!.leash,
+ currentEnteringRect,
+ enteringProgress,
+ enterAnimation!!
+ )
+ applyTransaction()
+ }
+
+ private fun applyTransform(
+ leash: SurfaceControl,
+ rect: RectF,
+ progress: Float,
+ animation: Animation
+ ) {
+ transformation.clear()
+ animation.getTransformationAt(progress, transformation)
+ applyTransform(leash, rect, transformation.alpha, transformation)
+ }
+
+ override fun finishAnimation() {
+ closeAnimation?.reset()
+ closeAnimation = null
+ enterAnimation?.reset()
+ enterAnimation = null
+ transformation.clear()
+ gestureProgress = 0f
+ super.finishAnimation()
+ }
+
+ /** Load customize animation before animation start. */
+ override fun prepareNextAnimation(
+ animationInfo: BackNavigationInfo.CustomAnimationInfo?,
+ letterboxColor: Int
+ ): Boolean {
+ super.prepareNextAnimation(animationInfo, letterboxColor)
+ if (animationInfo == null) return false
+ customAnimationLoader.loadAll(animationInfo)?.let { result ->
+ closeAnimation = result.closeAnimation
+ enterAnimation = result.enterAnimation
+ customizedBackgroundColor = result.backgroundColor
+ return true
+ }
+ return false
+ }
+
+ class AnimationLoadResult {
+ var closeAnimation: Animation? = null
+ var enterAnimation: Animation? = null
+ var backgroundColor = 0
+ }
+
+ companion object {
+ private const val PRE_COMMIT_MAX_PROGRESS = 0.2f
+ }
+}
+
+/** Helper class to load custom animation. */
+class CustomAnimationLoader(private val transitionAnimation: TransitionAnimation) {
+
+ /**
+ * Load both enter and exit animation for the close activity transition. Note that the result is
+ * only valid if the exit animation has set and loaded success. If the entering animation has
+ * not set(i.e. 0), here will load the default entering animation for it.
+ *
+ * @param animationInfo The information of customize animation, which can be set from
+ * [Activity.overrideActivityTransition] and/or [LayoutParams.windowAnimations]
+ */
+ fun loadAll(
+ animationInfo: BackNavigationInfo.CustomAnimationInfo
+ ): CustomCrossActivityBackAnimation.AnimationLoadResult? {
+ if (animationInfo.packageName.isEmpty()) return null
+ val close = loadAnimation(animationInfo, false) ?: return null
+ val open = loadAnimation(animationInfo, true)
+ val result = CustomCrossActivityBackAnimation.AnimationLoadResult()
+ result.closeAnimation = close
+ result.enterAnimation = open
+ result.backgroundColor = animationInfo.customBackground
+ return result
+ }
+
+ /**
+ * Load enter or exit animation from CustomAnimationInfo
+ *
+ * @param animationInfo The information for customize animation.
+ * @param enterAnimation true when load for enter animation, false for exit animation.
+ * @return Loaded animation.
+ */
+ fun loadAnimation(
+ animationInfo: BackNavigationInfo.CustomAnimationInfo,
+ enterAnimation: Boolean
+ ): Animation? {
+ var a: Animation? = null
+ // Activity#overrideActivityTransition has higher priority than windowAnimations
+ // Try to get animation from Activity#overrideActivityTransition
+ if (
+ enterAnimation && animationInfo.customEnterAnim != 0 ||
+ !enterAnimation && animationInfo.customExitAnim != 0
+ ) {
+ a =
+ transitionAnimation.loadAppTransitionAnimation(
+ animationInfo.packageName,
+ if (enterAnimation) animationInfo.customEnterAnim
+ else animationInfo.customExitAnim
+ )
+ } else if (animationInfo.windowAnimations != 0) {
+ // try to get animation from LayoutParams#windowAnimations
+ a =
+ transitionAnimation.loadAnimationAttr(
+ animationInfo.packageName,
+ animationInfo.windowAnimations,
+ if (enterAnimation) R.styleable.WindowAnimation_activityCloseEnterAnimation
+ else R.styleable.WindowAnimation_activityCloseExitAnimation,
+ false /* translucent */
+ )
+ }
+ // Only allow to load default animation for opening target.
+ if (a == null && enterAnimation) {
+ a = loadDefaultOpenAnimation()
+ }
+ if (a != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a)
+ } else {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "No custom animation loaded")
+ }
+ return a
+ }
+
+ private fun loadDefaultOpenAnimation(): Animation? {
+ return transitionAnimation.loadDefaultAnimationAttr(
+ R.styleable.WindowAnimation_activityCloseEnterAnimation,
+ false /* translucent */
+ )
+ }
+}
+
+private fun initializeAnimation(animation: Animation, bounds: Rect) {
+ val width = bounds.width()
+ val height = bounds.height()
+ animation.initialize(width, height, width, height)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
deleted file mode 100644
index e27b40e..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.FloatProperty;
-import android.view.Choreographer;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager.LayoutParams;
-import android.view.animation.Animation;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Transformation;
-import android.window.BackEvent;
-import android.window.BackMotionEvent;
-import android.window.BackNavigationInfo;
-import android.window.BackProgressAnimator;
-import android.window.IOnBackInvokedCallback;
-
-import com.android.internal.R;
-import com.android.internal.dynamicanimation.animation.SpringAnimation;
-import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
-
-import javax.inject.Inject;
-
-/** Class that handle customized close activity transition animation. */
-@ShellMainThread
-public class CustomizeActivityAnimation extends ShellBackAnimation {
- private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- private final BackAnimationRunner mBackAnimationRunner;
- private float mCornerRadius;
- private final SurfaceControl.Transaction mTransaction;
- private final BackAnimationBackground mBackground;
- private RemoteAnimationTarget mEnteringTarget;
- private RemoteAnimationTarget mClosingTarget;
- private IRemoteAnimationFinishedCallback mFinishCallback;
- /** Duration of post animation after gesture committed. */
- private static final int POST_ANIMATION_DURATION = 250;
-
- private static final int SCALE_FACTOR = 1000;
- private final SpringAnimation mProgressSpring;
- private float mLatestProgress = 0.0f;
-
- private static final float TARGET_COMMIT_PROGRESS = 0.5f;
-
- private final float[] mTmpFloat9 = new float[9];
- private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
-
- final CustomAnimationLoader mCustomAnimationLoader;
- private Animation mEnterAnimation;
- private Animation mCloseAnimation;
- private int mNextBackgroundColor;
- final Transformation mTransformation = new Transformation();
-
- private final Choreographer mChoreographer;
- private final Context mContext;
-
- @Inject
- public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
- this(context, background, new SurfaceControl.Transaction(), null);
- }
-
- CustomizeActivityAnimation(Context context, BackAnimationBackground background,
- SurfaceControl.Transaction transaction, Choreographer choreographer) {
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(
- new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
- mCustomAnimationLoader = new CustomAnimationLoader(context);
-
- mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
- mProgressSpring.setSpring(new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
- mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
- mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
- mContext = context;
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
- }
-
- private float getLatestProgress() {
- return mLatestProgress * SCALE_FACTOR;
- }
- private void setLatestProgress(float value) {
- mLatestProgress = value / SCALE_FACTOR;
- applyTransformTransaction(mLatestProgress);
- }
-
- private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
- new FloatProperty<>("enter") {
- @Override
- public void setValue(CustomizeActivityAnimation anim, float value) {
- anim.setLatestProgress(value);
- }
-
- @Override
- public Float get(CustomizeActivityAnimation object) {
- return object.getLatestProgress();
- }
- };
-
- // The target will lose focus when alpha == 0, so keep a minimum value for it.
- private static float keepMinimumAlpha(float transAlpha) {
- return Math.max(transAlpha, 0.005f);
- }
-
- private static void initializeAnimation(Animation animation, Rect bounds) {
- final int width = bounds.width();
- final int height = bounds.height();
- animation.initialize(width, height, width, height);
- }
-
- private void startBackAnimation() {
- if (mEnteringTarget == null || mClosingTarget == null
- || mCloseAnimation == null || mEnterAnimation == null) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
- return;
- }
- initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
- initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
-
- // Draw background with task background color.
- if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
- mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
- mNextBackgroundColor == Color.TRANSPARENT
- ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
- : mNextBackgroundColor,
- mTransaction);
- }
- }
-
- private void applyTransformTransaction(float progress) {
- if (mClosingTarget == null || mEnteringTarget == null) {
- return;
- }
- applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
- applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
- mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
- mTransaction.apply();
- }
-
- private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
- mTransformation.clear();
- animation.getTransformationAt(progress, mTransformation);
- mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
- mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
- mTransaction.setCornerRadius(leash, mCornerRadius);
- }
-
- void finishAnimation() {
- if (mCloseAnimation != null) {
- mCloseAnimation.reset();
- mCloseAnimation = null;
- }
- if (mEnterAnimation != null) {
- mEnterAnimation.reset();
- mEnterAnimation = null;
- }
- if (mEnteringTarget != null) {
- mEnteringTarget.leash.release();
- mEnteringTarget = null;
- }
- if (mClosingTarget != null) {
- mClosingTarget.leash.release();
- mClosingTarget = null;
- }
- if (mBackground != null) {
- mBackground.removeBackground(mTransaction);
- }
- mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
- mTransaction.apply();
- mTransformation.clear();
- mLatestProgress = 0;
- mNextBackgroundColor = Color.TRANSPARENT;
- if (mFinishCallback != null) {
- try {
- mFinishCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- mFinishCallback = null;
- }
- mProgressSpring.animateToFinalPosition(0);
- mProgressSpring.skipToEnd();
- }
-
- void onGestureProgress(@NonNull BackEvent backEvent) {
- if (mEnteringTarget == null || mClosingTarget == null
- || mCloseAnimation == null || mEnterAnimation == null) {
- return;
- }
-
- final float progress = backEvent.getProgress();
-
- float springProgress = (progress > 0.1f
- ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
- : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
-
- mProgressSpring.animateToFinalPosition(springProgress);
- }
-
- static float mapLinear(float x, float a1, float a2, float b1, float b2) {
- return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
- }
-
- void onGestureCommitted() {
- if (mEnteringTarget == null || mClosingTarget == null
- || mCloseAnimation == null || mEnterAnimation == null) {
- finishAnimation();
- return;
- }
- mProgressSpring.cancel();
-
- // Enter phase 2 of the animation
- final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
- .setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(mDecelerateInterpolator);
- valueAnimator.addUpdateListener(animation -> {
- float progress = (float) animation.getAnimatedValue();
- applyTransformTransaction(progress);
- });
-
- valueAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finishAnimation();
- }
- });
- valueAnimator.start();
- }
-
- /** Load customize animation before animation start. */
- @Override
- public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
- int letterboxColor) {
- if (animationInfo == null) {
- return false;
- }
- final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
- if (result != null) {
- mCloseAnimation = result.mCloseAnimation;
- mEnterAnimation = result.mEnterAnimation;
- mNextBackgroundColor = result.mBackgroundColor;
- return true;
- }
- return false;
- }
-
- @Override
- public BackAnimationRunner getRunner() {
- return mBackAnimationRunner;
- }
-
- private final class Callback extends IOnBackInvokedCallback.Default {
- @Override
- public void onBackStarted(BackMotionEvent backEvent) {
- // in case we're still animating an onBackCancelled event, let's remove the finish-
- // callback from the progress animator to prevent calling finishAnimation() before
- // restarting a new animation
- mProgressAnimator.removeOnBackCancelledFinishCallback();
-
- mProgressAnimator.onBackStarted(backEvent,
- CustomizeActivityAnimation.this::onGestureProgress);
- }
-
- @Override
- public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
- mProgressAnimator.onBackProgressed(backEvent);
- }
-
- @Override
- public void onBackCancelled() {
- mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
- }
-
- @Override
- public void onBackInvoked() {
- mProgressAnimator.reset();
- onGestureCommitted();
- }
- }
-
- private final class Runner extends IRemoteAnimationRunner.Default {
- @Override
- public void onAnimationStart(
- int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
- for (RemoteAnimationTarget a : apps) {
- if (a.mode == MODE_CLOSING) {
- mClosingTarget = a;
- }
- if (a.mode == MODE_OPENING) {
- mEnteringTarget = a;
- }
- }
- if (mCloseAnimation == null || mEnterAnimation == null) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW,
- "No animation loaded, should choose cross-activity animation?");
- }
-
- startBackAnimation();
- mFinishCallback = finishedCallback;
- }
-
- @Override
- public void onAnimationCancelled() {
- finishAnimation();
- }
- }
-
-
- static final class AnimationLoadResult {
- Animation mCloseAnimation;
- Animation mEnterAnimation;
- int mBackgroundColor;
- }
-
- /**
- * Helper class to load custom animation.
- */
- static class CustomAnimationLoader {
- final TransitionAnimation mTransitionAnimation;
-
- CustomAnimationLoader(Context context) {
- mTransitionAnimation = new TransitionAnimation(
- context, false /* debug */, "CustomizeBackAnimation");
- }
-
- /**
- * Load both enter and exit animation for the close activity transition.
- * Note that the result is only valid if the exit animation has set and loaded success.
- * If the entering animation has not set(i.e. 0), here will load the default entering
- * animation for it.
- *
- * @param animationInfo The information of customize animation, which can be set from
- * {@link Activity#overrideActivityTransition} and/or
- * {@link LayoutParams#windowAnimations}
- */
- AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
- if (animationInfo.getPackageName().isEmpty()) {
- return null;
- }
- final Animation close = loadAnimation(animationInfo, false);
- if (close == null) {
- return null;
- }
- final Animation open = loadAnimation(animationInfo, true);
- AnimationLoadResult result = new AnimationLoadResult();
- result.mCloseAnimation = close;
- result.mEnterAnimation = open;
- result.mBackgroundColor = animationInfo.getCustomBackground();
- return result;
- }
-
- /**
- * Load enter or exit animation from CustomAnimationInfo
- * @param animationInfo The information for customize animation.
- * @param enterAnimation true when load for enter animation, false for exit animation.
- * @return Loaded animation.
- */
- @Nullable
- Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
- boolean enterAnimation) {
- Animation a = null;
- // Activity#overrideActivityTransition has higher priority than windowAnimations
- // Try to get animation from Activity#overrideActivityTransition
- if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
- || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
- a = mTransitionAnimation.loadAppTransitionAnimation(
- animationInfo.getPackageName(),
- enterAnimation ? animationInfo.getCustomEnterAnim()
- : animationInfo.getCustomExitAnim());
- } else if (animationInfo.getWindowAnimations() != 0) {
- // try to get animation from LayoutParams#windowAnimations
- a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
- animationInfo.getWindowAnimations(), enterAnimation
- ? R.styleable.WindowAnimation_activityCloseEnterAnimation
- : R.styleable.WindowAnimation_activityCloseExitAnimation,
- false /* translucent */);
- }
- // Only allow to load default animation for opening target.
- if (a == null && enterAnimation) {
- a = loadDefaultOpenAnimation();
- }
- if (a != null) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
- } else {
- ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
- }
- return a;
- }
-
- private Animation loadDefaultOpenAnimation() {
- return mTransitionAnimation.loadDefaultAnimationAttr(
- R.styleable.WindowAnimation_activityCloseEnterAnimation,
- false /* translucent */);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
new file mode 100644
index 0000000..f33c5b9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.back
+
+import android.content.Context
+import android.view.Choreographer
+import android.view.SurfaceControl
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import javax.inject.Inject
+import kotlin.math.max
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class DefaultCrossActivityBackAnimation
+@Inject
+constructor(
+ context: Context,
+ background: BackAnimationBackground,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+) :
+ CrossActivityBackAnimation(
+ context,
+ background,
+ rootTaskDisplayAreaOrganizer,
+ SurfaceControl.Transaction(),
+ Choreographer.getInstance()
+ ) {
+
+ private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+ private val enteringStartOffset =
+ context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
+ override val allowEnteringYShift = true
+
+ override fun preparePreCommitEnteringRectMovement() {
+ // the entering target starts 96dp to the left of the screen edge...
+ startEnteringRect.set(startClosingRect)
+ startEnteringRect.offset(-enteringStartOffset, 0f)
+ // ...and gets scaled in sync with the closing target
+ targetEnteringRect.set(startEnteringRect)
+ targetEnteringRect.scaleCentered(MAX_SCALE)
+ }
+
+ override fun onGestureCommitted() {
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase. Let's update the start and target rects and kick
+ // off the animator in the superclass
+ startClosingRect.set(currentClosingRect)
+ startEnteringRect.set(currentEnteringRect)
+ targetEnteringRect.set(backAnimRect)
+ targetClosingRect.set(backAnimRect)
+ targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
+ super.onGestureCommitted()
+ }
+
+ override fun onPostCommitProgress(linearProgress: Float) {
+ super.onPostCommitProgress(linearProgress)
+ val closingAlpha = max(1f - linearProgress * 2, 0f)
+ val progress = postCommitInterpolator.getInterpolation(linearProgress)
+ currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
+ applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+ currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
+ applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ applyTransaction()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9e6c5fb..d8c1cab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1170,7 +1170,9 @@
* @param bubbleKey key of the bubble being dragged
*/
public void startBubbleDrag(String bubbleKey) {
- onBubbleDrag(bubbleKey, true /* isBeingDragged */);
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+ }
if (mBubbleStateListener != null) {
boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
Rect rect = new Rect();
@@ -1183,23 +1185,29 @@
}
/**
- * A bubble is no longer being dragged in Launcher. As was released in given location.
+ * A bubble is no longer being dragged in Launcher. And was released in given location.
* Will be called only when bubble bar is expanded.
*
- * @param bubbleKey key of the bubble being dragged
* @param location location where bubble was released
*/
- public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
+ public void stopBubbleDrag(BubbleBarLocation location) {
mBubblePositioner.setBubbleBarLocation(location);
- onBubbleDrag(bubbleKey, false /* isBeingDragged */);
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ }
}
- private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
- // TODO(b/330585402): collapse stack if any bubble is dragged
- if (mBubbleData.getSelectedBubble() != null
- && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
- // Should collapse/expand only if equals to selected bubble.
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+ /**
+ * A bubble was dragged and is released in dismiss target in Launcher.
+ *
+ * @param bubbleKey key of the bubble being dragged to dismiss target
+ */
+ public void dragBubbleToDismiss(String bubbleKey) {
+ String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
+ removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
+ // We did not remove the selected bubble. Expand it again
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
}
}
@@ -2358,12 +2366,6 @@
}
@Override
- public void removeBubble(String key) {
- mMainExecutor.execute(
- () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
- }
-
- @Override
public void removeAllBubbles() {
mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
}
@@ -2379,8 +2381,13 @@
}
@Override
- public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
- mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location));
+ public void stopBubbleDrag(BubbleBarLocation location) {
+ mMainExecutor.execute(() -> mController.stopBubbleDrag(location));
+ }
+
+ @Override
+ public void dragBubbleToDismiss(String key) {
+ mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key));
}
@Override
@@ -2397,7 +2404,10 @@
@Override
public void setBubbleBarBounds(Rect bubbleBarBounds) {
- mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+ mMainExecutor.execute(() -> {
+ mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+ if (mLayerView != null) mLayerView.updateExpandedView();
+ });
}
}
@@ -2709,6 +2719,15 @@
() -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
sensitiveNotificationProtectionActive));
}
+
+ @Override
+ public boolean canShowBubbleNotification() {
+ // in bubble bar mode, when the IME is visible we can't animate new bubbles.
+ if (BubbleController.this.isShowingAsBubbleBar()) {
+ return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+ }
+ return true;
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index ea30af5..26483c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -327,6 +327,14 @@
return mSelectedBubble;
}
+ /**
+ * Returns the key of the selected bubble, or null if no bubble is selected.
+ */
+ @Nullable
+ public String getSelectedBubbleKey() {
+ return mSelectedBubble != null ? mSelectedBubble.getKey() : null;
+ }
+
public BubbleOverflow getOverflow() {
return mOverflow;
}
@@ -1228,9 +1236,7 @@
public void dump(PrintWriter pw) {
pw.println("BubbleData state:");
pw.print(" selected: ");
- pw.println(mSelectedBubble != null
- ? mSelectedBubble.getKey()
- : "null");
+ pw.println(getSelectedBubbleKey());
pw.print(" expanded: ");
pw.println(mExpanded);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c4bbe32..a35a004 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -324,6 +324,11 @@
return 0;
}
+ /** Returns whether the IME is visible. */
+ public boolean getIsImeVisible() {
+ return mImeVisible;
+ }
+
/** Sets whether the IME is visible. **/
public void setImeVisible(boolean visible, int height) {
mImeVisible = visible;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 322088b..1d053f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -297,6 +297,15 @@
boolean sensitiveNotificationProtectionActive);
/**
+ * Determines whether Bubbles can show notifications.
+ *
+ * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble
+ * notification is suppressed and should be shown by the Notifications pipeline as regular
+ * notifications.
+ */
+ boolean canShowBubbleNotification();
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 66f77fa..1eff149 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -33,7 +33,7 @@
oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
- oneway void removeBubble(in String key) = 4;
+ oneway void dragBubbleToDismiss(in String key) = 4;
oneway void removeAllBubbles() = 5;
@@ -47,5 +47,5 @@
oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
- oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11;
+ oneway void stopBubbleDrag(in BubbleBarLocation location) = 11;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index a351cef..123cc7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -356,7 +356,7 @@
}
/** Updates the expanded view size and position. */
- private void updateExpandedView() {
+ public void updateExpandedView() {
if (mExpandedView == null || mExpandedBubble == null) return;
boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 5c292f1..bfac24b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,11 @@
*/
private boolean mHasShownUserAspectRatioSettingsButton = false;
+ /**
+ * This is true when the rechability education is displayed for the first time.
+ */
+ private boolean mIsFirstReachabilityEducationRunning;
+
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -252,9 +257,35 @@
removeLayouts(taskInfo.taskId);
return;
}
-
+ // We're showing the first reachability education so we ignore incoming TaskInfo
+ // until the education flow has completed or we double tap.
+ if (mIsFirstReachabilityEducationRunning) {
+ return;
+ }
+ if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+ if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) {
+ createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+ } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
+ // In this case the app is letterboxed and the letterbox education
+ // is disabled. In this case we need to understand if it's the first
+ // time we show the reachability education. When this is happening
+ // we need to ignore all the incoming TaskInfo until the education
+ // completes. If we come from a double tap we follow the normal flow.
+ final boolean topActivityPillarboxed =
+ taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+ final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
+ && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
+ final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
+ && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo);
+ if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) {
+ mIsFirstReachabilityEducationRunning = true;
+ mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId);
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ return;
+ }
+ }
+ }
createOrUpdateCompatLayout(taskInfo, taskListener);
- createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
@@ -589,6 +620,7 @@
private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener) {
// We need to update the UI otherwise it will not be shown until the user relaunches the app
+ mIsFirstReachabilityEducationRunning = false;
createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e729c7d..991fbaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,6 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
@@ -88,6 +87,7 @@
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a1910c5..fb0a1ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,7 +57,6 @@
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -77,6 +76,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
index 795bc1a..d2895b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -16,9 +16,9 @@
package com.android.wm.shell.dagger.back;
-import com.android.wm.shell.back.CrossActivityBackAnimation;
import com.android.wm.shell.back.CrossTaskBackAnimation;
-import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.CustomCrossActivityBackAnimation;
+import com.android.wm.shell.back.DefaultCrossActivityBackAnimation;
import com.android.wm.shell.back.ShellBackAnimation;
import com.android.wm.shell.back.ShellBackAnimationRegistry;
@@ -47,7 +47,7 @@
@Binds
@ShellBackAnimation.CrossActivity
ShellBackAnimation bindCrossActivityShellBackAnimation(
- CrossActivityBackAnimation crossActivityBackAnimation);
+ DefaultCrossActivityBackAnimation defaultCrossActivityBackAnimation);
/** Default cross task back animation */
@Binds
@@ -59,5 +59,5 @@
@Binds
@ShellBackAnimation.CustomizeActivity
ShellBackAnimation provideCustomizeActivityShellBackAnimation(
- CustomizeActivityAnimation customizeActivityAnimation);
+ CustomCrossActivityBackAnimation customCrossActivityBackAnimation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 9038aaa..0b7a3e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -39,6 +39,7 @@
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 2d508b2..7e0234e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -48,7 +48,6 @@
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
- var stashed: Boolean = false
)
// Token of the current wallpaper activity, used to remove it when the last task is removed
@@ -95,10 +94,8 @@
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
val visibleTasksCount = getVisibleTaskCount(displayId)
- val stashed = isStashed(displayId)
executor.execute {
visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
- visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
}
@@ -400,26 +397,6 @@
}
/**
- * Update stashed status on display with id [displayId]
- */
- fun setStashed(displayId: Int, stashed: Boolean) {
- val data = displayData.getOrCreate(displayId)
- val oldValue = data.stashed
- data.stashed = stashed
- if (oldValue != stashed) {
- KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: mark stashed=%b displayId=%d",
- stashed,
- displayId
- )
- visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onStashedChanged(displayId, stashed) }
- }
- }
- }
-
- /**
* Removes and returns the bounds saved before maximizing the given task.
*/
fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
@@ -433,13 +410,6 @@
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
}
- /**
- * Check if display with id [displayId] has desktop tasks stashed
- */
- fun isStashed(displayId: Int): Boolean {
- return displayData[displayId]?.stashed ?: false
- }
-
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
@@ -455,7 +425,6 @@
pw.println("${prefix}Display $displayId:")
pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
- pw.println("${innerPrefix}stashed=${data.stashed}")
}
}
@@ -477,11 +446,6 @@
* Called when the desktop changes the number of visible freeform tasks.
*/
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
-
- /**
- * Called when the desktop stashed status changes.
- */
- fun onStashedChanged(displayId: Int, stashed: Boolean) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
new file mode 100644
index 0000000..aa11a7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.desktopmode
+
+import android.util.Log
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Log Aster UIEvents for desktop windowing mode.
+ */
+@WMSingleton
+class DesktopModeUiEventLogger @Inject constructor(
+ private val mUiEventLogger: UiEventLogger,
+ private val mInstanceIdSequence: InstanceIdSequence
+) {
+ /**
+ * Logs an event for a CUI, on a particular package.
+ *
+ * @param uid The user id associated with the package the user is interacting with
+ * @param packageName The name of the package the user is interacting with
+ * @param event The event type to generate
+ */
+ fun log(uid: Int, packageName: String, event: DesktopUiEventEnum) {
+ if (packageName.isEmpty() || uid < 0) {
+ Log.d(TAG, "Skip logging since package name is empty or bad uid")
+ return
+ }
+ mUiEventLogger.log(event, uid, packageName)
+ }
+
+ /**
+ * Retrieves a new instance id for a new interaction.
+ */
+ fun getNewInstanceId(): InstanceId = mInstanceIdSequence.newInstanceId()
+
+ /**
+ * Logs an event as part of a particular CUI, on a particular package.
+ *
+ * @param instanceId The id identifying an interaction, potentially taking place across multiple
+ * surfaces. There should be a new id generated for each distinct CUI.
+ * @param uid The user id associated with the package the user is interacting with
+ * @param packageName The name of the package the user is interacting with
+ * @param event The event type to generate
+ */
+ fun logWithInstanceId(
+ instanceId: InstanceId,
+ uid: Int,
+ packageName: String,
+ event: DesktopUiEventEnum
+ ) {
+ if (packageName.isEmpty() || uid < 0) {
+ Log.d(TAG, "Skip logging since package name is empty or bad uid")
+ return
+ }
+ mUiEventLogger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ companion object {
+ /**
+ * Enums for logging desktop windowing mode UiEvents.
+ */
+ enum class DesktopUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the edge")
+ DESKTOP_WINDOW_EDGE_DRAG_RESIZE(1721),
+
+ @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the corner")
+ DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722),
+
+ @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
+
+ @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
+ DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724);
+
+ override fun getId(): Int = mId
+ }
+
+ private const val TAG = "DesktopModeUiEventLogger"
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b0d5923..2dc4573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -72,6 +72,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -227,7 +228,7 @@
bringDesktopAppsToFront(displayId, wct)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/255649902): ensure remote transition is supplied once state is introduced
+ // TODO(b/309014605): ensure remote transition is supplied once state is introduced
val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
val handler = remoteTransition?.let {
OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -240,34 +241,6 @@
}
}
- /**
- * Stash desktop tasks on display with id [displayId].
- *
- * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
- * launched in this state will be added to the desktop. Existing desktop tasks will be brought
- * back to front during the launch.
- */
- fun stashDesktopApps(displayId: Int) {
- if (DesktopModeStatus.isStashingEnabled()) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
- desktopModeTaskRepository.setStashed(displayId, true)
- }
- }
-
- /**
- * Clear the stashed state for the given display
- */
- fun hideStashedDesktopApps(displayId: Int) {
- if (DesktopModeStatus.isStashingEnabled()) {
- KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: hideStashedApps displayId=%d",
- displayId
- )
- desktopModeTaskRepository.setStashed(displayId, false)
- }
- }
-
/** Get number of tasks that are marked as visible */
fun getVisibleTaskCount(displayId: Int): Int {
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
@@ -871,8 +844,6 @@
val result = triggerTask?.let { task ->
when {
request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
- // If display has tasks stashed, handle as stashed launch
- task.isStashed -> handleStashedTaskLaunch(task, transition)
// Check if the task has a top transparent activity
shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -911,12 +882,8 @@
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
- private val TaskInfo.isStashed: Boolean
- get() = desktopModeTaskRepository.isStashed(displayId)
-
- private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
- return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
- }
+ private fun shouldLaunchAsModal(task: TaskInfo) =
+ Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
return Flags.enableDesktopWindowingWallpaperActivity() &&
@@ -976,24 +943,6 @@
return null
}
- private fun handleStashedTaskLaunch(
- task: RunningTaskInfo,
- transition: IBinder
- ): WindowContainerTransaction {
- KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: launch apps with stashed on transition taskId=%d",
- task.taskId
- )
- val wct = WindowContainerTransaction()
- val taskToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
- addMoveToDesktopChanges(wct, task)
- desktopModeTaskRepository.setStashed(task.displayId, false)
- addPendingMinimizeTransition(transition, taskToMinimize)
- return wct
- }
-
// Always launch transparent tasks in fullscreen.
private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
// Already fullscreen, no-op.
@@ -1426,16 +1375,6 @@
l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
}
}
-
- override fun onStashedChanged(displayId: Int, stashed: Boolean) {
- KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onStashedChanged display=%d stashed=%b",
- displayId,
- stashed
- )
- remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
- }
}
init {
@@ -1467,20 +1406,6 @@
) { c -> c.showDesktopApps(displayId, remoteTransition) }
}
- override fun stashDesktopApps(displayId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "stashDesktopApps"
- ) { c -> c.stashDesktopApps(displayId) }
- }
-
- override fun hideStashedDesktopApps(displayId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "hideStashedDesktopApps"
- ) { c -> c.hideStashedDesktopApps(displayId) }
- }
-
override fun showDesktopApp(taskId: Int) {
ExecutorUtils.executeRemoteCallWithTaskPermission(
controller,
@@ -1488,6 +1413,20 @@
) { c -> c.moveTaskToFront(taskId) }
}
+ override fun stashDesktopApps(displayId: Int) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: stashDesktopApps is deprecated"
+ )
+ }
+
+ override fun hideStashedDesktopApps(displayId: Int) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: hideStashedDesktopApps is deprecated"
+ )
+ }
+
override fun getVisibleTaskCount(displayId: Int): Int {
val result = IntArray(1)
ExecutorUtils.executeRemoteCallWithTaskPermission(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 3404d37..0f88384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -25,6 +25,7 @@
import androidx.annotation.VisibleForTesting
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
import com.android.wm.shell.util.KtProtoLog
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 451e09c..dae75f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,6 +23,7 @@
import android.window.TransitionInfo
import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index fa43522..c36f8de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -28,10 +28,10 @@
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
- /** Stash apps on the desktop to allow launching another app from home screen */
+ /** @deprecated use {@link #showDesktopApps} instead. */
void stashDesktopApps(int displayId);
- /** Hide apps that may be stashed */
+ /** @deprecated this is no longer supported. */
void hideStashedDesktopApps(int displayId);
/** Bring task with the given id to front */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 8ed87f2..8ebdfdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -25,6 +25,6 @@
/** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
- /** Desktop task stashed status has changed. */
+ /** @deprecated this is no longer supported. */
oneway void onStashedChanged(int displayId, boolean stashed);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index a414a55..e0e2e706 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -27,9 +27,9 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 9eaf7e4..c79eef7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
@@ -83,6 +84,7 @@
* @see KeyguardTransitions
*/
private IRemoteTransition mExitTransition = null;
+ private IRemoteTransition mAppearTransition = null;
private IRemoteTransition mOccludeTransition = null;
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
@@ -170,26 +172,28 @@
// Choose a transition applicable for the changes and keyguard state.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
- return startAnimation(mExitTransition,
- "going-away",
+ return startAnimation(mExitTransition, "going-away",
transition, info, startTransaction, finishTransaction, finishCallback);
}
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+ return startAnimation(mAppearTransition, "appearing",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+
+
// Occlude/unocclude animations are only played if the keyguard is locked.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
if (hasOpeningDream(info)) {
- return startAnimation(mOccludeByDreamTransition,
- "occlude-by-dream",
+ return startAnimation(mOccludeByDreamTransition, "occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
- return startAnimation(mOccludeTransition,
- "occlude",
+ return startAnimation(mOccludeTransition, "occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
} else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- return startAnimation(mUnoccludeTransition,
- "unocclude",
+ return startAnimation(mUnoccludeTransition, "unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
}
@@ -359,11 +363,13 @@
@Override
public void register(
IRemoteTransition exitTransition,
+ IRemoteTransition appearTransition,
IRemoteTransition occludeTransition,
IRemoteTransition occludeByDreamTransition,
IRemoteTransition unoccludeTransition) {
mMainExecutor.execute(() -> {
mExitTransition = exitTransition;
+ mAppearTransition = appearTransition;
mOccludeTransition = occludeTransition;
mOccludeByDreamTransition = occludeByDreamTransition;
mUnoccludeTransition = unoccludeTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index 4215b2c..b7245b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -35,6 +35,7 @@
*/
default void register(
@NonNull IRemoteTransition unlockTransition,
+ @NonNull IRemoteTransition appearTransition,
@NonNull IRemoteTransition occludeTransition,
@NonNull IRemoteTransition occludeByDreamTransition,
@NonNull IRemoteTransition unoccludeTransition) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 7b1ef5c..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -39,7 +39,7 @@
* @param isSysUiStateValid Is SysUI state valid or not.
* @param flag Current SysUI state.
*/
- default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+ default void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e885262..e1657f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -854,7 +854,8 @@
mPipUiEventLoggerLogger.log(uiEventEnum);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
+ "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity,
+ mPipTransitionState, mTaskInfo.taskId);
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index fdde3ee..2082756 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -824,12 +824,10 @@
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo) {
startTransaction.apply();
- if (info.getChanges().isEmpty()) {
+ final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info);
+ if (pipChange == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "removePipImmediately is called with empty changes");
- } else {
- finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
- mPipDisplayLayoutState.getDisplayBounds());
+ "removePipImmediately is called without pip change");
}
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 139cde2..85f9194 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -847,7 +847,7 @@
}
}
- private void onSystemUiStateChanged(boolean isValidState, int flag) {
+ private void onSystemUiStateChanged(boolean isValidState, long flag) {
mTouchHandler.onSystemUiStateChanged(isValidState);
}
@@ -1195,7 +1195,7 @@
}
@Override
- public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+ public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
mMainExecutor.execute(() -> {
PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index a8611d9..c53e7fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -50,9 +50,9 @@
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.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
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 b10176d..8e97068 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
@@ -47,6 +47,7 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -67,6 +68,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -2836,7 +2838,7 @@
mSplitLayout.setFreezeDividerWindow(false);
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
- boolean hasEnteringPip = false;
+ TransitionInfo.Change pipChange = null;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
@@ -2847,7 +2849,7 @@
}
if (mMixedHandler.isEnteringPip(change, transitType)) {
- hasEnteringPip = true;
+ pipChange = change;
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2899,9 +2901,19 @@
}
}
- if (hasEnteringPip) {
+ if (pipChange != null) {
+ TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
+ mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
+ getSplitItemStage(pipChange.getLastParent()));
+ if (pipReplacingChange != null) {
+ // Set an enter transition for when startAnimation gets called again
+ mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ }
+
mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
- startTransaction, finishTransaction, finishCallback);
+ startTransaction, finishTransaction, finishCallback,
+ pipReplacingChange != null);
notifySplitAnimationFinished();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1e305c5..f77c80d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -177,9 +177,11 @@
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d "
+ + "taskActivity=%s",
taskInfo.taskId, taskInfo.parentTaskId,
- mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
+ mRootTaskInfo != null ? mRootTaskInfo.taskId : -1,
+ taskInfo.baseActivity);
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
@@ -213,6 +215,8 @@
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s",
+ taskInfo.taskId, taskInfo.baseActivity);
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index e419462..e07e1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -45,6 +45,7 @@
import com.android.internal.R;
+import java.io.Closeable;
import java.util.function.LongConsumer;
/**
@@ -100,7 +101,7 @@
* Drawable pre-drawing the scaled icon in a separate thread to increase the speed of the
* final drawing.
*/
- private static class ImmobileIconDrawable extends Drawable {
+ private static class ImmobileIconDrawable extends Drawable implements Closeable {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
| Paint.FILTER_BITMAP_FLAG);
private final Matrix mMatrix = new Matrix();
@@ -154,6 +155,16 @@
public int getOpacity() {
return 1;
}
+
+ @Override
+ public void close() {
+ synchronized (mPaint) {
+ if (mIconBitmap != null) {
+ mIconBitmap.recycle();
+ mIconBitmap = null;
+ }
+ }
+ }
}
/**
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 968b27b..bcacecb 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
@@ -77,6 +77,7 @@
private ActivityEmbeddingController mActivityEmbeddingController;
abstract static class MixedTransition {
+ /** Entering Pip from split, breaks split. */
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -103,6 +104,9 @@
/** Enter pip from one of the Activity Embedding windows. */
static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
+ /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
+ static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -484,9 +488,11 @@
// TODO(b/287704263): Remove when split/mixed are reversed.
public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- Transitions.TransitionFinishCallback finishCallback) {
- final MixedTransition mixed = createDefaultMixedTransition(
- MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
+ Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) {
+ int type = replacingPip
+ ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT
+ : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT;
+ final MixedTransition mixed = createDefaultMixedTransition(type, transition);
mActiveTransitions.add(mixed);
Transitions.TransitionFinishCallback callback = wct -> {
mActiveTransitions.remove(mixed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index b028bd6..0ada749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -76,7 +76,12 @@
info, startTransaction, finishTransaction, finishCallback);
case TYPE_ENTER_PIP_FROM_SPLIT ->
animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
- finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ false);
+ case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT ->
+ animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ true);
case TYPE_KEYGUARD ->
animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
mKeyguardHandler, mPipHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index ffc0b76..e8b01b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -23,11 +23,15 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -45,7 +49,8 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler,
- @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler,
+ boolean replacingPip) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ "entering PIP while Split-Screen is foreground.");
TransitionInfo.Change pipChange = null;
@@ -99,7 +104,7 @@
// we need a separate one to send over to launcher.
SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
@SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
- if (splitHandler.isSplitScreenVisible()) {
+ if (splitHandler.isSplitScreenVisible() && !replacingPip) {
// The non-going home case, we could be pip-ing one of the split stages and keep
// showing the other
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -115,11 +120,12 @@
break;
}
}
+
+ // Let split update internal state for dismiss.
+ splitHandler.prepareDismissAnimation(topStageToKeep,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
}
- // Let split update internal state for dismiss.
- splitHandler.prepareDismissAnimation(topStageToKeep,
- EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
- finishTransaction);
// We are trying to accommodate launcher's close animation which can't handle the
// divider-bar, so if split-handler is closing the divider-bar, just hide it and
@@ -152,6 +158,44 @@
return true;
}
+ /**
+ * Check to see if we're only closing split to enter pip or if we're replacing pip with
+ * another task. If we are replacing, this will return the change for the task we are replacing
+ * pip with
+ *
+ * @param info Any number of changes
+ * @param pipChange TransitionInfo.Change indicating the task that is being pipped
+ * @param splitMainStageRootId MainStage's rootTaskInfo's id
+ * @param splitSideStageRootId SideStage's rootTaskInfo's id
+ * @param lastPipSplitStage The last stage that {@param pipChange} was in
+ * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null}
+ * otherwise
+ */
+ @Nullable
+ public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info,
+ TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId,
+ @SplitScreen.StageType int lastPipSplitStage) {
+ int lastPipParentTask = -1;
+ if (lastPipSplitStage == STAGE_TYPE_MAIN) {
+ lastPipParentTask = splitMainStageRootId;
+ } else if (lastPipSplitStage == STAGE_TYPE_SIDE) {
+ lastPipParentTask = splitSideStageRootId;
+ }
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange || !isOpeningMode(change.getMode())) {
+ // Ignore the change/task that's going into Pip or not opening
+ continue;
+ }
+
+ if (change.getTaskInfo().parentTaskId == lastPipParentTask) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
return change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index d6e64cf..9fc6702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -142,7 +142,8 @@
&& mSplitHandler.getSplitItemPosition(change.getLastParent())
!= SPLIT_POSITION_UNDEFINED) {
return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
- finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4d3c763..6224543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,7 +31,6 @@
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -567,15 +566,15 @@
final int mode = change.getMode();
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- if (isOpening
- // This is for when an activity launches while a different transition is
- // collecting.
- || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+ if (isOpening) {
// put on top
return zSplitLine + numChanges - i;
- } else {
+ } else if (isClosing) {
// put on bottom
return zSplitLine - i;
+ } else {
+ // maintain relative ordering (put all changes in the animating layer)
+ return zSplitLine + numChanges - i;
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
if (isOpening) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 1897560..6adbe4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -49,8 +49,12 @@
public PerfettoTransitionTracer() {
Producer.init(InitArguments.DEFAULTS);
- mDataSource.register(
- new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .build();
+ mDataSource.register(params);
}
/**
@@ -214,8 +218,6 @@
}
os.end(mappingsToken);
-
- ctx.flush();
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index dfdb58a..9afb057 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -84,12 +84,12 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 4c347ad..4d4dc3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -66,8 +66,8 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index de6c035..5418254 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -52,7 +52,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index 4dd14f4..f69a90c 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 5c86a38..b76d065 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index bc486c2..984abf8 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -32,7 +32,7 @@
/**
* Test launching a new activity from bubble.
*
- * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:ChangeActiveActivityFromBubbleTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 2a9b107..886b70c 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -35,7 +35,7 @@
/**
* Test launching a new activity from bubble.
*
- * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:DragToDismissBubbleScreenTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 9ef49c1..2ee53f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -38,7 +38,7 @@
/**
* Test launching a new activity from bubble.
*
- * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest`
+ * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleOnLocksreenTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index ef7fbfb..463fe0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -29,7 +29,7 @@
/**
* Test launching a new activity from bubble.
*
- * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 87224b15..8df5056 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -29,7 +29,7 @@
/**
* Test creating a bubble notification
*
- * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:SendBubbleNotificationTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index aa70c09..041978c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
index c7c804f..a66dfb4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index 17cace0..d485b82 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -21,6 +21,7 @@
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
@@ -133,9 +134,8 @@
}
),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP),
AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index 214bdfa..85715db 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index f99b4b2..f6f3aa4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -120,7 +120,7 @@
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
- private CrossActivityBackAnimation mCrossActivityBackAnimation;
+ private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
private CrossTaskBackAnimation mCrossTaskBackAnimation;
private ShellBackAnimationRegistry mShellBackAnimationRegistry;
@@ -135,13 +135,14 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground,
- mRootTaskDisplayAreaOrganizer);
+ mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
+ mAnimationBackground, mRootTaskDisplayAreaOrganizer);
mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
mShellBackAnimationRegistry =
- new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation,
- /* dialogCloseAnimation= */ null,
- new CustomizeActivityAnimation(mContext, mAnimationBackground),
+ new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
+ mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
+ new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
+ mRootTaskDisplayAreaOrganizer),
/* defaultBackToHomeAnimation= */ null);
mController =
new BackAnimationController(
@@ -582,7 +583,7 @@
@Test
public void testBackToActivity() throws RemoteException {
verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mCrossActivityBackAnimation.getRunner());
+ mDefaultCrossActivityBackAnimation.getRunner());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
new file mode 100644
index 0000000..8bf0111
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.f
+ */
+package com.android.wm.shell.back
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.AppCompatTaskInfo
+import android.app.WindowConfiguration
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.RemoteException
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Choreographer
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.animation.Animation
+import android.window.BackEvent
+import android.window.BackMotionEvent
+import android.window.BackNavigationInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import junit.framework.TestCase.assertEquals
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class CustomCrossActivityBackAnimationTest : ShellTestCase() {
+ @Mock private lateinit var backAnimationBackground: BackAnimationBackground
+ @Mock private lateinit var mockCloseAnimation: Animation
+ @Mock private lateinit var mockOpenAnimation: Animation
+ @Mock private lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var transitionAnimation: TransitionAnimation
+ @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
+ @Mock private lateinit var transaction: Transaction
+
+ private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
+ private lateinit var customAnimationLoader: CustomAnimationLoader
+
+ @Before
+ @Throws(Exception::class)
+ fun setUp() {
+ customAnimationLoader = CustomAnimationLoader(transitionAnimation)
+ customCrossActivityBackAnimation =
+ CustomCrossActivityBackAnimation(
+ context,
+ backAnimationBackground,
+ rootTaskDisplayAreaOrganizer,
+ transaction,
+ mock(Choreographer::class.java),
+ customAnimationLoader
+ )
+
+ whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
+ .thenReturn(mockOpenAnimation)
+ whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(CLOSE_RES_ID)))
+ .thenReturn(mockCloseAnimation)
+ whenever(transaction.setColor(any(), any())).thenReturn(transaction)
+ whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+ whenever(transaction.setCrop(any(), any())).thenReturn(transaction)
+ whenever(transaction.setRelativeLayer(any(), any(), anyInt())).thenReturn(transaction)
+ spy(customCrossActivityBackAnimation)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun receiveFinishAfterInvoke() {
+ val finishCalled = startCustomAnimation()
+ try {
+ customCrossActivityBackAnimation.getRunner().callback.onBackInvoked()
+ } catch (r: RemoteException) {
+ Assert.fail("onBackInvoked throw remote exception")
+ }
+ finishCalled.await(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun receiveFinishAfterCancel() {
+ val finishCalled = startCustomAnimation()
+ try {
+ customCrossActivityBackAnimation.getRunner().callback.onBackCancelled()
+ } catch (r: RemoteException) {
+ Assert.fail("onBackCancelled throw remote exception")
+ }
+ finishCalled.await(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun receiveFinishWithoutAnimationAfterInvoke() {
+ val finishCalled = startCustomAnimation(targets = arrayOf())
+ try {
+ customCrossActivityBackAnimation.getRunner().callback.onBackInvoked()
+ } catch (r: RemoteException) {
+ Assert.fail("onBackInvoked throw remote exception")
+ }
+ finishCalled.await(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ fun testLoadCustomAnimation() {
+ testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 0)
+ }
+
+ @Test
+ fun testLoadCustomAnimationNoEnter() {
+ testLoadCustomAnimation(0, CLOSE_RES_ID, 0)
+ }
+
+ @Test
+ fun testLoadWindowAnimations() {
+ testLoadCustomAnimation(0, 0, 30)
+ }
+
+ @Test
+ fun testCustomAnimationHigherThanWindowAnimations() {
+ testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 30)
+ }
+
+ private fun testLoadCustomAnimation(enterResId: Int, exitResId: Int, windowAnimations: Int) {
+ val builder =
+ BackNavigationInfo.Builder()
+ .setCustomAnimation(PACKAGE_NAME, enterResId, exitResId, Color.GREEN)
+ .setWindowAnimations(PACKAGE_NAME, windowAnimations)
+ val info = builder.build().customAnimationInfo!!
+ whenever(
+ transitionAnimation.loadAnimationAttr(
+ eq(PACKAGE_NAME),
+ eq(windowAnimations),
+ anyInt(),
+ anyBoolean()
+ )
+ )
+ .thenReturn(mockCloseAnimation)
+ whenever(transitionAnimation.loadDefaultAnimationAttr(anyInt(), anyBoolean()))
+ .thenReturn(mockOpenAnimation)
+ val result = customAnimationLoader.loadAll(info)!!
+ if (exitResId != 0) {
+ if (enterResId == 0) {
+ verify(transitionAnimation, never())
+ .loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(enterResId))
+ verify(transitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean())
+ } else {
+ assertEquals(result.enterAnimation, mockOpenAnimation)
+ }
+ assertEquals(result.backgroundColor.toLong(), Color.GREEN.toLong())
+ assertEquals(result.closeAnimation, mockCloseAnimation)
+ verify(transitionAnimation, never())
+ .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean())
+ } else if (windowAnimations != 0) {
+ verify(transitionAnimation, times(2))
+ .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean())
+ Assert.assertEquals(result.closeAnimation, mockCloseAnimation)
+ }
+ }
+
+ private fun startCustomAnimation(
+ targets: Array<RemoteAnimationTarget> =
+ arrayOf(createAnimationTarget(false), createAnimationTarget(true))
+ ): CountDownLatch {
+ val backNavigationInfo =
+ BackNavigationInfo.Builder()
+ .setCustomAnimation(PACKAGE_NAME, OPEN_RES_ID, CLOSE_RES_ID, /*backgroundColor*/ 0)
+ .build()
+ customCrossActivityBackAnimation.prepareNextAnimation(
+ backNavigationInfo.customAnimationInfo,
+ 0
+ )
+ val finishCalled = CountDownLatch(1)
+ val finishCallback = Runnable { finishCalled.countDown() }
+ customCrossActivityBackAnimation
+ .getRunner()
+ .startAnimation(targets, null, null, finishCallback)
+ customCrossActivityBackAnimation.runner.callback.onBackStarted(backMotionEventFrom(0f, 0f))
+ if (targets.isNotEmpty()) {
+ verify(mockCloseAnimation)
+ .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE))
+ verify(mockOpenAnimation)
+ .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE))
+ }
+ return finishCalled
+ }
+
+ private fun backMotionEventFrom(touchX: Float, progress: Float) =
+ BackMotionEvent(
+ /* touchX = */ touchX,
+ /* touchY = */ 0f,
+ /* progress = */ progress,
+ /* velocityX = */ 0f,
+ /* velocityY = */ 0f,
+ /* triggerBack = */ false,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT,
+ /* departingAnimationTarget = */ null
+ )
+
+ private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget {
+ val topWindowLeash = SurfaceControl()
+ val taskInfo = RunningTaskInfo()
+ taskInfo.appCompatTaskInfo = appCompatTaskInfo
+ taskInfo.taskDescription = ActivityManager.TaskDescription()
+ return RemoteAnimationTarget(
+ 1,
+ if (open) RemoteAnimationTarget.MODE_OPENING else RemoteAnimationTarget.MODE_CLOSING,
+ topWindowLeash,
+ false,
+ Rect(),
+ Rect(),
+ -1,
+ Point(0, 0),
+ Rect(0, 0, BOUND_SIZE, BOUND_SIZE),
+ Rect(),
+ WindowConfiguration(),
+ true,
+ null,
+ null,
+ taskInfo,
+ false,
+ -1
+ )
+ }
+
+ companion object {
+ private const val BOUND_SIZE = 100
+ private const val OPEN_RES_ID = 1000
+ private const val CLOSE_RES_ID = 1001
+ private const val PACKAGE_NAME = "TestPackage"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
deleted file mode 100644
index 158d640..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.WindowConfiguration;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Choreographer;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.window.BackNavigationInfo;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner.class)
-public class CustomizeActivityAnimationTest extends ShellTestCase {
- private static final int BOUND_SIZE = 100;
- @Mock
- private BackAnimationBackground mBackAnimationBackground;
- @Mock
- private Animation mMockCloseAnimation;
- @Mock
- private Animation mMockOpenAnimation;
-
- private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
- @Before
- public void setUp() throws Exception {
- mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
- mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
- mock(Choreographer.class));
- spyOn(mCustomizeActivityAnimation);
- spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
- }
-
- RemoteAnimationTarget createAnimationTarget(boolean open) {
- SurfaceControl topWindowLeash = new SurfaceControl();
- return new RemoteAnimationTarget(1,
- open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
- topWindowLeash, false, new Rect(), new Rect(), -1,
- new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
- new WindowConfiguration(), true, null, null, null, false, -1);
- }
-
- @Test
- public void receiveFinishAfterInvoke() throws InterruptedException {
- spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
- doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .loadAnimation(any(), eq(false));
- doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .loadAnimation(any(), eq(true));
-
- mCustomizeActivityAnimation.prepareNextAnimation(
- new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
- final RemoteAnimationTarget close = createAnimationTarget(false);
- final RemoteAnimationTarget open = createAnimationTarget(true);
- // start animation with remote animation targets
- final CountDownLatch finishCalled = new CountDownLatch(1);
- final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation
- .getRunner()
- .startAnimation(
- new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
- verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
- eq(BOUND_SIZE), eq(BOUND_SIZE));
- verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
- eq(BOUND_SIZE), eq(BOUND_SIZE));
-
- try {
- mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
- } catch (RemoteException r) {
- fail("onBackInvoked throw remote exception");
- }
- verify(mCustomizeActivityAnimation).onGestureCommitted();
- finishCalled.await(1, TimeUnit.SECONDS);
- }
-
- @Test
- public void receiveFinishAfterCancel() throws InterruptedException {
- spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
- doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .loadAnimation(any(), eq(false));
- doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .loadAnimation(any(), eq(true));
-
- mCustomizeActivityAnimation.prepareNextAnimation(
- new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
- final RemoteAnimationTarget close = createAnimationTarget(false);
- final RemoteAnimationTarget open = createAnimationTarget(true);
- // start animation with remote animation targets
- final CountDownLatch finishCalled = new CountDownLatch(1);
- final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation
- .getRunner()
- .startAnimation(
- new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
- verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
- eq(BOUND_SIZE), eq(BOUND_SIZE));
- verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
- eq(BOUND_SIZE), eq(BOUND_SIZE));
-
- try {
- mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
- } catch (RemoteException r) {
- fail("onBackCancelled throw remote exception");
- }
- finishCalled.await(1, TimeUnit.SECONDS);
- }
-
- @Test
- public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
- mCustomizeActivityAnimation.prepareNextAnimation(
- new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
- // start animation without any remote animation targets
- final CountDownLatch finishCalled = new CountDownLatch(1);
- final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation
- .getRunner()
- .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
-
- try {
- mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
- } catch (RemoteException r) {
- fail("onBackInvoked throw remote exception");
- }
- verify(mCustomizeActivityAnimation).onGestureCommitted();
- finishCalled.await(1, TimeUnit.SECONDS);
- }
-
- @Test
- public void testLoadCustomAnimation() {
- testLoadCustomAnimation(10, 20, 0);
- }
-
- @Test
- public void testLoadCustomAnimationNoEnter() {
- testLoadCustomAnimation(0, 10, 0);
- }
-
- @Test
- public void testLoadWindowAnimations() {
- testLoadCustomAnimation(0, 0, 30);
- }
-
- @Test
- public void testCustomAnimationHigherThanWindowAnimations() {
- testLoadCustomAnimation(10, 20, 30);
- }
-
- private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
- final String testPackage = "TestPackage";
- BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
- .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
- .setWindowAnimations(testPackage, windowAnimations);
- final BackNavigationInfo.CustomAnimationInfo info = builder.build()
- .getCustomAnimationInfo();
-
- doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
- .mTransitionAnimation)
- .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
- doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
- .mTransitionAnimation)
- .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
- doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
- .mTransitionAnimation)
- .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
- doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
- .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
-
- CustomizeActivityAnimation.AnimationLoadResult result =
- mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
-
- if (exitResId != 0) {
- if (enterResId == 0) {
- verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
- never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
- verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
- .loadDefaultAnimationAttr(anyInt(), anyBoolean());
- } else {
- assertEquals(result.mEnterAnimation, mMockOpenAnimation);
- }
- assertEquals(result.mBackgroundColor, Color.GREEN);
- assertEquals(result.mCloseAnimation, mMockCloseAnimation);
- verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
- .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
- } else if (windowAnimations != 0) {
- verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
- times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
- assertEquals(result.mCloseAnimation, mMockCloseAnimation);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index afae653..9c00864 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -668,6 +668,18 @@
Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
}
+ @Test
+ public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
+
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
@CameraCompatControlState int cameraCompatControlState) {
return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
@@ -694,6 +706,8 @@
taskInfo.isVisible = isVisible;
taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
+ taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true;
+ taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 60a7dcd..2a2483d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -41,6 +41,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index dca7be1..8f59f30 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -182,18 +182,6 @@
}
@Test
- fun addListener_notifiesStashed() {
- repo.setStashed(DEFAULT_DISPLAY, true)
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- executor.flushAll()
-
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
- }
-
- @Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
@@ -400,65 +388,6 @@
}
@Test
- fun setStashed_stateIsUpdatedForTheDisplay() {
- repo.setStashed(DEFAULT_DISPLAY, true)
- assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
- assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
- fun setStashed_notifyListener() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isFalse()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
- }
-
- @Test
- fun setStashed_secondCallDoesNotNotify() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- repo.setStashed(DEFAULT_DISPLAY, true)
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
- }
-
- @Test
- fun setStashed_tracksPerDisplay() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
-
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedOnSecondaryDisplay).isFalse()
-
- repo.setStashed(SECOND_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedOnSecondaryDisplay).isTrue()
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isFalse()
- assertThat(listener.stashedOnSecondaryDisplay).isTrue()
- }
-
- @Test
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
@@ -598,12 +527,6 @@
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
- var stashedOnDefaultDisplay = false
- var stashedOnSecondaryDisplay = false
-
- var stashedChangesOnDefaultDisplay = 0
- var stashedChangesOnSecondaryDisplay = 0
-
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
@@ -617,20 +540,6 @@
else -> fail("Visible task listener received unexpected display id: $displayId")
}
}
-
- override fun onStashedChanged(displayId: Int, stashed: Boolean) {
- when (displayId) {
- DEFAULT_DISPLAY -> {
- stashedOnDefaultDisplay = stashed
- stashedChangesOnDefaultDisplay++
- }
- SECOND_DISPLAY -> {
- stashedOnSecondaryDisplay = stashed
- stashedChangesOnDefaultDisplay++
- }
- else -> fail("Visible task listener received unexpected display id: $displayId")
- }
- }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
new file mode 100644
index 0000000..285e5b6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.desktopmode
+
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.Companion.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test class for [DesktopModeUiEventLogger]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeUiEventLoggerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeUiEventLoggerTest : ShellTestCase() {
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var logger: DesktopModeUiEventLogger
+ private val instanceIdSequence = InstanceIdSequence(10)
+
+
+ @Before
+ fun setUp() {
+ uiEventLoggerFake = UiEventLoggerFake()
+ logger = DesktopModeUiEventLogger(uiEventLoggerFake, instanceIdSequence)
+ }
+
+ @Test
+ fun log_invalidUid_eventNotLogged() {
+ logger.log(-1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+ }
+
+ @Test
+ fun log_emptyPackageName_eventNotLogged() {
+ logger.log(UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+ }
+
+ @Test
+ fun log_eventLogged() {
+ val event =
+ DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ logger.log(UID, PACKAGE_NAME, event)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+ assertThat(uiEventLoggerFake[0].instanceId).isNull()
+ assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+ assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+ }
+
+ @Test
+ fun getNewInstanceId() {
+ val first = logger.getNewInstanceId()
+ assertThat(first).isNotEqualTo(logger.getNewInstanceId())
+ }
+
+ @Test
+ fun logWithInstanceId_invalidUid_eventNotLogged() {
+ logger.logWithInstanceId(INSTANCE_ID, -1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+ }
+
+ @Test
+ fun logWithInstanceId_emptyPackageName_eventNotLogged() {
+ logger.logWithInstanceId(INSTANCE_ID, UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+ }
+
+ @Test
+ fun logWithInstanceId_eventLogged() {
+ val event =
+ DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+ assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(INSTANCE_ID)
+ assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+ assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+ }
+
+
+ companion object {
+ private val INSTANCE_ID = InstanceId.fakeInstanceId(0)
+ private const val UID = 10
+ private const val PACKAGE_NAME = "com.foo"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 3f76c4f..f67da55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -79,6 +79,7 @@
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -1044,29 +1045,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
- markTaskHidden(stashedFreeformTask)
-
- val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result).isNotNull()
- result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
- assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
-
- // Stashed state should be cleared
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
fun handleRequest_freeformTask_freeformVisible_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1133,27 +1111,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
- markTaskHidden(stashedFreeformTask)
-
- val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(freeformTask))
- assertThat(result).isNotNull()
- result?.assertReorderSequence(stashedFreeformTask, freeformTask)
-
- // Stashed state should be cleared
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1269,29 +1226,6 @@
}
@Test
- fun stashDesktopApps_stateUpdates() {
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
- assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
- }
-
- @Test
- fun hideStashedDesktopApps_stateUpdates() {
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
- desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
- controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
-
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- // Check that second display is not affected
- assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
- }
-
- @Test
fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
val task = setUpFreeformTask()
clearInvocations(launchAdjacentController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 539d5b8..3c488ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -32,6 +32,7 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 665077b..cd68c69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -35,8 +35,8 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 240324b..884cb6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -67,8 +67,8 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 7d19f3c..aa2cee7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -60,8 +60,8 @@
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.desktopmode.DesktopModeStatus
import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4eb44d7..8b8cd11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -75,7 +75,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import org.junit.Before;
diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
index b511244..6196589 100644
--- a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
+++ b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+ default_team: "trendy_team_android_resources",
}
cc_fuzz {
@@ -31,7 +32,7 @@
static_libs: ["libgmock"],
target: {
android: {
- shared_libs:[
+ shared_libs: [
"libandroidfw",
"libbase",
"libcutils",
@@ -52,4 +53,15 @@
],
},
},
+ fuzz_config: {
+ cc: [
+ "android-resources@google.com",
+ ],
+ componentid: 568761,
+ description: "The fuzzer targets the APIs of libandroidfw",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
+ },
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 753a699..7c1c5b4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -336,6 +336,7 @@
"jni/android_graphics_animation_NativeInterpolatorFactory.cpp",
"jni/android_graphics_animation_RenderNodeAnimator.cpp",
"jni/android_graphics_Canvas.cpp",
+ "jni/android_graphics_Color.cpp",
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index fd9915a..70a9ef0 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -46,6 +46,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
@@ -87,6 +88,7 @@
{"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
{"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
{"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)},
+ {"android.graphics.Color", REG_JNI(register_android_graphics_Color)},
{"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
{"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
{"android.graphics.CreateJavaOutputStreamAdaptor",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index fb0cdb0..6ace396 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -49,6 +49,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
@@ -98,6 +99,7 @@
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Canvas),
+ REG_JNI(register_android_graphics_Color),
// This needs to be before register_android_graphics_Graphics, or the latter
// will not be able to find the jmethodID for ColorSpace.get().
REG_JNI(register_android_graphics_ColorSpace),
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index a952be0..2a057e7 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -36,25 +36,6 @@
return 0; \
}
-static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray)
-{
- SkScalar hsv[3];
- SkRGBToHSV(red, green, blue, hsv);
-
- AutoJavaFloatArray autoHSV(env, hsvArray, 3);
- float* values = autoHSV.ptr();
- for (int i = 0; i < 3; i++) {
- values[i] = SkScalarToFloat(hsv[i]);
- }
-}
-
-static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
-{
- AutoJavaFloatArray autoHSV(env, hsvArray, 3);
- SkScalar* hsv = autoHSV.ptr();
- return static_cast<jint>(SkHSVToColor(alpha, hsv));
-}
-
///////////////////////////////////////////////////////////////////////////////////////////////
static void Shader_safeUnref(SkShader* shader) {
@@ -409,11 +390,6 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static const JNINativeMethod gColorMethods[] = {
- { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV },
- { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor }
-};
-
static const JNINativeMethod gShaderMethods[] = {
{ "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer },
};
@@ -456,8 +432,6 @@
int register_android_graphics_Shader(JNIEnv* env)
{
- android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
- NELEM(gColorMethods));
android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods,
NELEM(gShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
diff --git a/libs/hwui/jni/android_graphics_Color.cpp b/libs/hwui/jni/android_graphics_Color.cpp
new file mode 100644
index 0000000..c22b8b9
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Color.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GraphicsJNI.h"
+
+#include "SkColor.h"
+
+using namespace android;
+
+static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue,
+ jfloatArray hsvArray)
+{
+ SkScalar hsv[3];
+ SkRGBToHSV(red, green, blue, hsv);
+
+ AutoJavaFloatArray autoHSV(env, hsvArray, 3);
+ float* values = autoHSV.ptr();
+ for (int i = 0; i < 3; i++) {
+ values[i] = SkScalarToFloat(hsv[i]);
+ }
+}
+
+static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
+{
+ AutoJavaFloatArray autoHSV(env, hsvArray, 3);
+ SkScalar* hsv = autoHSV.ptr();
+ return static_cast<jint>(SkHSVToColor(alpha, hsv));
+}
+
+static const JNINativeMethod gColorMethods[] = {
+ { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV },
+ { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor }
+};
+
+namespace android {
+
+int register_android_graphics_Color(JNIEnv* env) {
+ return android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
+ NELEM(gColorMethods));
+}
+
+}; // namespace android
diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp
index 63d3f83..d06206b 100644
--- a/libs/hwui/jni/android_graphics_ColorSpace.cpp
+++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp
@@ -148,7 +148,7 @@
namespace android {
int register_android_graphics_ColorSpace(JNIEnv* env) {
- return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb",
+ return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb$Native",
gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods));
}
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index c0d791a..eedc069 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -326,9 +326,6 @@
};
static const JNINativeMethod methods[] = {
- {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer},
- {"nCreate","(J)J", (void*) SkMatrixGlue::create},
-
// ------- @FastNative below here ---------------
{"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
{"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z",
@@ -388,9 +385,6 @@
int register_android_graphics_Matrix(JNIEnv* env) {
// Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx
// for why we need it.
- //
- // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood
- // environment, so we just always run it.
RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods,
NELEM(extra_methods));
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 5cf5a1d..f1ee325 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -467,10 +467,10 @@
std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
/*
- * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback
+ * Using ui::LogicalDisplayId::INVALID for displayId here to avoid removing the callback
* if a TouchSpotController with the same display is removed.
*/
- mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func);
+ mContext.addAnimationCallback(ui::LogicalDisplayId::INVALID, func);
}
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 70e5c24..c6430f7 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -109,7 +109,7 @@
struct Locked {
Presentation presentation;
- ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE;
+ ui::LogicalDisplayId pointerDisplayId = ui::LogicalDisplayId::INVALID;
std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 070c90c..e147c56 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -174,7 +174,7 @@
int32_t layer{0};
float alpha{1.0f};
SpriteTransformationMatrix transformationMatrix;
- ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId displayId{ui::LogicalDisplayId::DEFAULT};
sp<SurfaceControl> surfaceControl;
int32_t surfaceWidth{0};
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 7a13380..2dcb1f1 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -166,7 +166,7 @@
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT);
+ void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -335,23 +335,23 @@
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ui::ADISPLAY_ID_DEFAULT);
+ ui::LogicalDisplayId::DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Marking the display to skip screenshot should update sprite as well
- mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true);
+ mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ui::ADISPLAY_ID_DEFAULT);
+ ui::LogicalDisplayId::DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Reset flag and verify again
- mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false);
+ mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ui::ADISPLAY_ID_DEFAULT);
+ ui::LogicalDisplayId::DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 6d4cc3a..cf7aea4 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,8 +64,10 @@
}
public final class NfcAdapter {
+ method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
method public void disableForegroundDispatch(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 310130e..a33e225 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -3,9 +3,7 @@
public final class NfcAdapter {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index e43d104..06098de 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1106,6 +1106,9 @@
* {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
* operation is complete.
*
+ * <p>This API is only allowed to be called by system apps
+ * or apps which are Device Owner or Profile Owner.
+ *
* <p>If this returns true, then either NFC is already on, or
* a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
* to indicate a state transition. If this returns false, then
@@ -1113,9 +1116,8 @@
* NFC on (for example we are in airplane mode and NFC is not
* toggleable in airplane mode on this platform).
*
- * @hide
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean enable() {
try {
@@ -1146,15 +1148,17 @@
* {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
* operation is complete.
*
+ * <p>This API is only allowed to be called by system apps
+ * or apps which are Device Owner or Profile Owner.
+ *
* <p>If this returns true, then either NFC is already off, or
* a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
* to indicate a state transition. If this returns false, then
* there is some problem that prevents an attempt to turn
* NFC off.
*
- * @hide
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean disable() {
try {
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index f674b06a..c3c74a6 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -404,6 +405,7 @@
*
* @param frame A description of the polling frame.
*/
+ @SuppressLint("OnNameExpected")
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public void processPollingFrames(@NonNull List<PollingFrame> frame) {
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 73b29db..cb2a48c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -93,3 +93,11 @@
description: "Enable NFC OEM extension support"
bug: "331206243"
}
+
+flag {
+ name: "nfc_state_change"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enable nfc state change API"
+ bug: "319934052"
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index d4a8110..7bc25ed 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -174,11 +174,8 @@
onUserCancel()
} else {
Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
- " re-displaying our UI.")
- uiState = uiState.copy(
- selectedEntry = null,
- providerActivityState = ProviderActivityState.NOT_APPLICABLE,
- )
+ " re-displaying our UI.")
+ resetUiStateForReLaunch()
}
} else {
if (entry != null) {
@@ -202,6 +199,15 @@
}
}
+ // Resets UI states for any situation that re-launches the UI
+ private fun resetUiStateForReLaunch() {
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ uiState = uiState.copy(
+ selectedEntry = null,
+ providerActivityState = ProviderActivityState.NOT_APPLICABLE,
+ )
+ }
+
fun onLastLockedAuthEntryNotFoundError() {
Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry")
onInternalError()
@@ -502,4 +508,4 @@
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 25ac3c9..635dc42 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -172,7 +172,7 @@
// This is for testing only now
private boolean mEnableWhenCompleted;
- private boolean mOneShot;
+ private boolean mOneShot = true;
private boolean mHideNotification;
private InstallationAsyncTask.Progress mInstallTaskProgress;
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
index 4fa8822..ad3b44f 100644
--- a/packages/PrintSpooler/TEST_MAPPING
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -8,5 +8,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "PrintSpoolerOutOfProcessTests"
+ }
]
}
diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml
index 3cc64a6..76fa7b9 100644
--- a/packages/PrintSpooler/res/values-night/themes.xml
+++ b/packages/PrintSpooler/res/values-night/themes.xml
@@ -24,6 +24,7 @@
<style name="Theme.SelectPrinterActivity"
parent="android:style/Theme.DeviceDefault">
<item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index bd96025..22842f7 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -24,6 +24,7 @@
parent="android:style/Theme.DeviceDefault.Light">
<item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
<item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index d25d5dc..ff09084 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -785,6 +785,9 @@
} else {
onPrinterUnavailable(printerInfo);
}
+ if (mPrinterRegistry != null) {
+ mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
+ }
mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
index 30cb993..a762ad3 100644
--- a/packages/SettingsLib/DataStore/README.md
+++ b/packages/SettingsLib/DataStore/README.md
@@ -1,55 +1,93 @@
# Datastore library
-This library aims to manage datastore in a consistent way.
+This library provides consistent API for data management (including backup,
+restore, and metrics logging) on Android platform.
+
+Notably, it is designed to be flexible and could be utilized for a wide range of
+data store besides the settings preferences.
## Overview
-A datastore is required to extend the `BackupRestoreStorage` class and implement
-either `Observable` or `KeyedObservable` interface, which enforces:
+In the high-level design, a persistent datastore aims to support two key
+characteristics:
-- Backup and restore: Datastore should support
- [data backup](https://developer.android.com/guide/topics/data/backup) to
- preserve user experiences on a new device.
-- Observer pattern: The
- [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
- monitor data change in the datastore and
- - trigger
- [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
- automatically.
- - track data change event to log metrics.
- - update internal state and take action.
+- **observable**: triggers backup and metrics logging whenever data is
+ changed.
+- **transferable**: offers users with a seamless experience by backing up and
+ restoring data on to new devices.
+
+More specifically, Android framework supports
+[data backup](https://developer.android.com/guide/topics/data/backup) to
+preserve user experiences on a new device. And the
+[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+monitor data change.
### Backup and restore
-The Android backup framework provides
+Currently, the Android backup framework provides
[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
and
[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
-to back up a datastore. However, there are several caveats when implement
-`BackupHelper`:
+to facilitate data backup. However, there are several caveats to consider when
+implementing `BackupHelper`:
-- performBackup: The data is updated incrementally but it is not well
+- *performBackup*: The data is updated incrementally but it is not well
documented. The `ParcelFileDescriptor` state parameters are normally ignored
and data is updated even there is no change.
-- restoreEntity: The implementation must take care not to seek or close the
- underlying data source, nor read more than size() bytes from the stream when
- restore (see
+- *restoreEntity*: The implementation must take care not to seek or close the
+ underlying data source, nor read more than `size()` bytes from the stream
+ when restore (see
[BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
- It is possible a `BackupHelper` prevents other `BackupHelper`s from
- restoring data.
-- writeNewStateDescription: Existing implementations rarely notice that this
- callback is invoked after all entities are restored, and check if necessary
- data are all restored in `restoreEntity` (e.g.
+ It is possible that a `BackupHelper` interferes with the restore process of
+ other `BackupHelper`s.
+- *writeNewStateDescription*: Existing implementations rarely notice that this
+ callback is invoked after *all* entities are restored. Instead, they check
+ if necessary data are all restored in the `restoreEntity` (e.g.
[BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
which is not robust sometimes.
-This library provides more clear API and offers some improvements:
+The datastore library will mitigate these problems by providing alternative
+APIs. For instance, library users make use of `InputStream` / `OutputStream` to
+back up and restore data directly.
-- The implementation only needs to focus on the `BackupRestoreEntity`
- interface. The `InputStream` of restore will ensure bounded data are read,
- and close the stream will be no-op.
-- The library computes checksum of the backup data automatically, so that
- unchanged data will not be sent to Android backup system.
+### Observer pattern
+
+In the current implementation, the Android backup framework requires a manual
+call to
+[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)).
+However, it's often observed that this API call is forgotten when using
+`SharedPreferences`. Additionally, there's a common need to log metrics when
+data changed. To address these limitations, datastore API employed the observer
+pattern.
+
+### API design and advantages
+
+Datastore must extend the `BackupRestoreStorage` class (subclass of
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)).
+The data in a datastore is group by entity, which is represented by
+`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus
+on the `BackupRestoreEntity`.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. There are builtin thread-safe implementations of the
+two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use
+delegation to simplify the code.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`
+directly. To back up other file based storage, extend the
+`BackupRestoreFileStorage` class.
+
+Here are some highlights of the library:
+
+- The restore `InputStream` will ensure bounded data are read, and close the
+ stream is no-op. That being said, all entities are isolated.
+- Data checksum is computed automatically, unchanged data will not be sent to
+ Android backup system.
- Data compression is supported:
- ZIP best compression is enabled by default, no extra effort needs to be
taken.
@@ -67,98 +105,159 @@
successfully restored in those older versions. This is achieved by extending
the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
treat each file as an entity and do the backup / restore.
-- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
- do the invocation (see next section).
+- Manual `BackupManager.dataChanged` call is unnecessary now, the framework
+ will invoke the API automatically.
-### Observer pattern
+## Usages
-Manual `BackupManager.dataChanged` call is required by current backup framework.
-In practice, it is found that `SharedPreferences` usages foget to invoke the
-API. Besides, there are common use cases to log metrics when data is changed.
-Consequently, observer pattern is employed to resolve the issues.
+This section provides [examples](example/ExampleStorage.kt) of datastore.
-If the datastore is key-value based (e.g. `SharedPreferences`), implements the
-`KeyedObservable` interface to offer fine-grained observer. Otherwise,
-implements `Observable`. The library provides thread-safe implementations
-(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
-helpful.
-
-Keep in mind that the implementation should call `KeyedObservable.notifyChange`
-/ `Observable.notifyChange` whenever internal data is changed, so that the
-registered observer will be notified properly.
-
-## Usage and example
-
-For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
-back up other file based storage, extend the `BackupRestoreFileStorage` class.
-
-Here is an example of customized datastore, which has a string to back up:
+Here is a datastore with a string data:
```kotlin
-class MyDataStore : ObservableBackupRestoreStorage() {
- // Another option is make it a StringEntity type and maintain a String field inside StringEntity
- @Volatile // backup/restore happens on Binder thread
- var data: String? = null
- private set
+class ExampleStorage : ObservableBackupRestoreStorage() {
+ @Volatile // field is manipulated by multiple threads, synchronization might be needed
+ var data: String? = null
+ private set
- fun setData(data: String?) {
- this.data = data
- notifyChange(ChangeReason.UPDATE)
+ @AnyThread
+ fun setData(data: String?) {
+ this.data = data
+ // call notifyChange to trigger backup and metrics logging whenever data is changed
+ if (data != null) {
+ notifyChange(ChangeReason.UPDATE)
+ } else {
+ notifyChange(ChangeReason.DELETE)
}
-
- override val name: String
- get() = "MyData"
-
- override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
- listOf(StringEntity("data"))
-
- private inner class StringEntity(override val key: String) : BackupRestoreEntity {
- override fun backup(
- backupContext: BackupContext,
- outputStream: OutputStream,
- ) =
- if (data != null) {
- outputStream.write(data!!.toByteArray(UTF_8))
- EntityBackupResult.UPDATE
- } else {
- EntityBackupResult.DELETE
- }
-
- override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
- data = String(inputStream.readAllBytes(), UTF_8)
- // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
- }
- }
-
- override fun onRestoreFinished() {
- // TODO: Update state with the restored data. Use this callback instead "restore()" in case
- // the restore action involves several entities.
- // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
- }
-}
-```
-
-In the application class:
-
-```kotlin
-class MyApplication : Application() {
- override fun onCreate() {
- super.onCreate();
- BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
}
-}
-```
-In the custom `BackupAgentHelper` class:
+ override val name: String
+ get() = "ExampleStorage"
-```kotlin
-class MyBackupAgentHelper : BackupAgentHelper() {
- override fun onCreate() {
- BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ override fun enableRestore(): Boolean {
+ return true // check condition like flag, environment, etc.
+ }
+
+ override fun enableBackup(backupContext: BackupContext): Boolean {
+ return true // check condition like flag, environment, etc.
+ }
+
+ @BinderThread
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(backupContext: BackupContext, outputStream: OutputStream) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE // delete existing backup data
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ // DO NOT call setData API here, which will trigger notifyChange unexpectedly.
+ // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE)
+ // later to notify observers.
+ data = String(inputStream.readBytes(), UTF_8)
+ // Handle restored data in onRestoreFinished() callback
+ }
}
override fun onRestoreFinished() {
- BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ // TODO: Update state with the restored data. Use this callback instead of "restore()" in
+ // case the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
}
}
```
+
+And this is a datastore with key value data:
+
+```kotlin
+class ExampleKeyValueStorage :
+ BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() {
+ // thread safe data structure
+ private val map = ConcurrentHashMap<String, String>()
+
+ override val name: String
+ get() = "ExampleKeyValueStorage"
+
+ fun updateData(key: String, value: String?) {
+ if (value != null) {
+ map[key] = value
+ notifyChange(ChangeReason.UPDATE)
+ } else {
+ map.remove(key)
+ notifyChange(ChangeReason.DELETE)
+ }
+ }
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(createMapBackupRestoreEntity())
+
+ private fun createMapBackupRestoreEntity() =
+ object : BackupRestoreEntity {
+ override val key: String
+ get() = "map"
+
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ): EntityBackupResult {
+ // Use TreeMap to achieve predictable and stable order, so that data will not be
+ // updated to Android backup backend if there is only order change.
+ val copy = TreeMap(map)
+ if (copy.isEmpty()) return EntityBackupResult.DELETE
+ val dataOutputStream = DataOutputStream(outputStream)
+ dataOutputStream.writeInt(copy.size)
+ for ((key, value) in copy) {
+ dataOutputStream.writeUTF(key)
+ dataOutputStream.writeUTF(value)
+ }
+ return EntityBackupResult.UPDATE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ val dataInputString = DataInputStream(inputStream)
+ repeat(dataInputString.readInt()) {
+ val key = dataInputString.readUTF()
+ val value = dataInputString.readUTF()
+ map[key] = value
+ }
+ }
+ }
+}
+```
+
+All the datastore should be added in the application class:
+
+```kotlin
+class ExampleApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ BackupRestoreStorageManager.getInstance(this)
+ .add(ExampleStorage(), ExampleKeyValueStorage())
+ }
+}
+```
+
+Additionally, inject datastore to the custom `BackupAgentHelper` class:
+
+```kotlin
+class ExampleBackupAgent : BackupAgentHelper() {
+ override fun onCreate() {
+ super.onCreate()
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this)
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished()
+ }
+}
+```
+
+## Development
+
+Please preserve the code coverage ratio during development. The current line
+coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**.
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
index 817ee4c..6720e5c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -23,7 +23,11 @@
import java.io.InputStream
import java.io.OutputStream
-/** Entity for back up and restore. */
+/**
+ * Entity for back up and restore.
+ *
+ * Note that backup/restore callback is invoked on the binder thread.
+ */
interface BackupRestoreEntity {
/**
* Key of the entity.
@@ -45,9 +49,12 @@
/**
* Backs up the entity.
*
+ * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data
+ * will be backed up needlessly.
+ *
* @param backupContext context for backup
* @param outputStream output stream to back up data
- * @return false if backup file is deleted, otherwise true
+ * @return backup result
*/
@BinderThread
@Throws(IOException::class)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index 935f9cc..284c97b 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,6 +22,7 @@
import android.app.backup.BackupHelper
import android.os.ParcelFileDescriptor
import android.util.Log
+import androidx.annotation.BinderThread
import androidx.annotation.VisibleForTesting
import androidx.collection.MutableScatterMap
import com.google.common.io.ByteStreams
@@ -38,16 +39,22 @@
import java.util.zip.CheckedInputStream
import java.util.zip.CheckedOutputStream
import java.util.zip.Checksum
+import javax.annotation.concurrent.ThreadSafe
internal const val LOG_TAG = "BackupRestoreStorage"
/**
- * Storage with backup and restore support. Subclass must implement either [Observable] or
- * [KeyedObservable] interface.
+ * Storage with backup and restore support.
+ *
+ * Subclass MUST
+ * - implement either [Observable] or [KeyedObservable] interface.
+ * - be thread safe, backup/restore happens on Binder thread, while general data read/write
+ * operations occur on other threads.
*
* The storage is identified by a unique string [name] and data set is split into entities
* ([BackupRestoreEntity]).
*/
+@ThreadSafe
abstract class BackupRestoreStorage : BackupHelper {
/**
* A unique string used to disambiguate the various storages within backup agent.
@@ -68,7 +75,7 @@
@VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null
/** Entities to back up and restore. */
- abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
+ @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
/** Default codec used to encode/decode the entity data. */
open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
@@ -134,7 +141,11 @@
Log.i(LOG_TAG, "[$name] Backup end")
}
- /** Returns if backup is enabled. */
+ /**
+ * Returns if backup is enabled.
+ *
+ * If disabled, [performBackup] will be no-op, all entities backup are skipped.
+ */
open fun enableBackup(backupContext: BackupContext): Boolean = true
open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
@@ -172,7 +183,11 @@
private fun ensureEntities(): List<BackupRestoreEntity> =
entities ?: createBackupRestoreEntities().also { entities = it }
- /** Returns if restore is enabled. */
+ /**
+ * Returns if restore is enabled.
+ *
+ * If disabled, [restoreEntity] will be no-op, all entities restore are skipped.
+ */
open fun enableRestore(): Boolean = true
open fun wrapRestoreInputStream(
@@ -188,12 +203,13 @@
}
final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+ if (!enableRestore()) return
entities = null // clear to reduce memory footprint
newState.writeAndClearEntityStates()
onRestoreFinished()
}
- /** Callbacks when restore finished. */
+ /** Callbacks when entity data are all restored. */
open fun onRestoreFinished() {}
@VisibleForTesting
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
index 99998ff..26534ba 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
@@ -248,6 +248,15 @@
}
@Test
+ fun writeNewStateDescription_restoreDisabled() {
+ val storage = spy(TestStorage().apply { enabled = false })
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ storage.writeNewStateDescription(it)
+ }
+ verify(storage, never()).onRestoreFinished()
+ }
+
+ @Test
fun backupAndRestore() {
val storage = spy(TestStorage(entity1, entity2))
val backupAgentHelper = BackupAgentHelper()
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 05507e0..493818b 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -80,14 +80,15 @@
continue;
}
final URLSpan urlSpan = (URLSpan) clickable;
- if (!urlSpan.getURL().startsWith(INTENT_URL_PREFIX)) {
+ final String url = urlSpan.getURL();
+ if (url == null || !url.startsWith(INTENT_URL_PREFIX)) {
continue;
}
final int start = spannable.getSpanStart(urlSpan);
final int end = spannable.getSpanEnd(urlSpan);
spannable.removeSpan(urlSpan);
try {
- final Intent intent = Intent.parseUri(urlSpan.getURL(), Intent.URI_INTENT_SCHEME);
+ final Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
final ClickableSpan clickableSpan =
new ClickableSpan() {
@Override
@@ -98,7 +99,7 @@
};
spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (URISyntaxException e) {
- Log.e(TAG, "Invalid URI " + urlSpan.getURL(), e);
+ Log.e(TAG, "Invalid URI " + url, e);
}
}
title.setText(spannable);
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 6407810..c3a91a2 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -21,6 +21,7 @@
"SettingsLibColor",
"androidx.preference_preference",
"lottie",
+ "settingslib_illustrationpreference_flags_lib",
],
sdk_version: "system_current",
@@ -31,3 +32,24 @@
"com.android.permission",
],
}
+
+aconfig_declarations {
+ name: "settingslib_illustrationpreference_flags",
+ package: "com.android.settingslib.widget.flags",
+ container: "system",
+ srcs: [
+ "aconfig/illustrationpreference.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "settingslib_illustrationpreference_flags_lib",
+ aconfig_declarations: "settingslib_illustrationpreference_flags",
+
+ min_sdk_version: "30",
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
+}
diff --git a/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig
new file mode 100644
index 0000000..e566d89
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.settingslib.widget.flags"
+container: "system"
+
+flag {
+ name: "auto_hide_empty_lottie_res"
+ namespace: "android_settings"
+ description: "Hides IllustrationPreference when Lottie resource is an empty file"
+ bug: "337873972"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 815a101..bbf0315 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -39,12 +39,14 @@
import androidx.preference.PreferenceViewHolder;
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
+import com.android.settingslib.widget.flags.Flags;
import com.android.settingslib.widget.preference.illustration.R;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
/**
@@ -142,7 +144,7 @@
illustrationFrame.setLayoutParams(lp);
illustrationView.setCacheComposition(mCacheComposition);
- handleImageWithAnimation(illustrationView);
+ handleImageWithAnimation(illustrationView, illustrationFrame);
handleImageFrameMaxHeight(backgroundView, illustrationView);
if (mIsAutoScale) {
@@ -332,7 +334,8 @@
}
}
- private void handleImageWithAnimation(LottieAnimationView illustrationView) {
+ private void handleImageWithAnimation(LottieAnimationView illustrationView,
+ ViewGroup container) {
if (mImageDrawable != null) {
resetAnimations(illustrationView);
illustrationView.setImageDrawable(mImageDrawable);
@@ -356,6 +359,25 @@
}
if (mImageResId > 0) {
+ if (Flags.autoHideEmptyLottieRes()) {
+ // Check if resource is empty
+ try (InputStream is = illustrationView.getResources()
+ .openRawResource(mImageResId)) {
+ int check = is.read();
+ // -1 = end of stream. if first read is end of stream, then file is empty
+ if (check == -1) {
+ illustrationView.setVisibility(View.GONE);
+ container.setVisibility(View.GONE);
+ return;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to open Lottie raw resource", e);
+ }
+
+ illustrationView.setVisibility(View.VISIBLE);
+ container.setVisibility(View.VISIBLE);
+ }
+
resetAnimations(illustrationView);
illustrationView.setImageResource(mImageResId);
final Drawable drawable = illustrationView.getDrawable();
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
index 4ced9f2..cece966 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -16,8 +16,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+ <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected -->
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
index 285ab73..eba9c2c 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -19,12 +19,12 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:radius="?android:attr/dialogCornerRadius" />
+ android:radius="@dimen/settingslib_preference_corner_radius" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
index e417307..5c60f37 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -19,15 +19,15 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:topLeftRadius="0dp"
- android:bottomLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="0dp"
- android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ android:topLeftRadius="4dp"
+ android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:topRightRadius="4dp"
+ android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
new file mode 100644
index 0000000..de64efd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:topLeftRadius="4dp"
+ android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:topRightRadius="4dp"
+ android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index e964657..dd70f4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -19,12 +19,12 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:radius="1dp" />
+ android:radius="4dp" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
new file mode 100644
index 0000000..fffc6c8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:radius="4dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
new file mode 100644
index 0000000..f83e3b1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:radius="@dimen/settingslib_preference_corner_radius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
index a9d69c2..ab79d18 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -19,15 +19,15 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomRightRadius="0dp" />
+ android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomLeftRadius="4dp"
+ android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomRightRadius="4dp" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
new file mode 100644
index 0000000..112ec73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomLeftRadius="4dp"
+ android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomRightRadius="4dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 221e8f5..94ff02d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -37,8 +37,11 @@
<!-- Material next track off color-->
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+ <!-- Dialog text button color. -->
+ <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
<color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index dc2d3dc..8b95016 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -37,8 +37,11 @@
<!-- Material next track off color-->
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+ <!-- Dialog text button color. -->
+ <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
new file mode 100644
index 0000000..d783956
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="settingslib_preference_corner_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 4147813..45667f5 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha05"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha08"
}
subprojects {
@@ -41,7 +41,7 @@
defaultConfig {
minSdk = 21
- targetSdk = 34
+ targetSdk = 35
}
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 6344501..4aa57b3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,17 +53,17 @@
dependencies {
api(project(":SettingsLibColor"))
- api("androidx.appcompat:appcompat:1.7.0-alpha03")
+ api("androidx.appcompat:appcompat:1.7.0-beta01")
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-alpha03")
+ api("androidx.compose.material3:material3:1.3.0-alpha06")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha05")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha08")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index da1ee77..e867a8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -21,6 +21,7 @@
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -30,7 +31,6 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
-import androidx.core.view.WindowCompat
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavGraphBuilder
@@ -82,7 +82,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
- WindowCompat.setDecorFitsSystemWindows(window, false)
+ enableEdgeToEdge()
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 88ba4b0..1a10bf0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -27,14 +27,13 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
-const val URL_SPAN_TAG = "URL_SPAN_TAG"
-
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = LocalContext.current.resources
@@ -97,12 +96,9 @@
start: Int,
end: Int,
) {
- addStyle(
- SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
- start,
- end,
+ val url = LinkAnnotation.Url(
+ url = urlSpan.url,
+ style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
)
- if (!urlSpan.url.isNullOrEmpty()) {
- addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
- }
+ addLink(url, start, end)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 36cd136..9a344c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -35,6 +35,7 @@
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
@@ -42,11 +43,11 @@
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.TopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableFloatStateOf
@@ -79,7 +80,12 @@
import kotlin.math.max
import kotlin.math.roundToInt
-@OptIn(ExperimentalMaterial3Api::class)
+private val windowInsets: WindowInsets
+ @Composable
+ @NonRestartableComposable
+ get() = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
+
@Composable
internal fun CustomizedTopAppBar(
title: @Composable () -> Unit,
@@ -91,7 +97,7 @@
titleTextStyle = MaterialTheme.typography.titleMedium,
navigationIcon = navigationIcon,
actions = actions,
- windowInsets = TopAppBarDefaults.windowInsets,
+ windowInsets = windowInsets,
colors = topAppBarColors(),
)
}
@@ -118,7 +124,7 @@
navigationIcon = navigationIcon,
actions = actions,
colors = topAppBarColors(),
- windowInsets = TopAppBarDefaults.windowInsets,
+ windowInsets = windowInsets,
pinnedHeight = ContainerHeight,
scrollBehavior = scrollBehavior,
)
@@ -336,7 +342,7 @@
Modifier.draggable(
orientation = Orientation.Vertical,
state = rememberDraggableState { delta ->
- scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+ scrollBehavior.state.heightOffset += delta
},
onDragStopped = { velocity ->
settleAppBar(
@@ -411,6 +417,7 @@
* (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
* the actions are optional.
*
+ * @param modifier a [Modifier]
* @param heightPx the total height this layout is capped to
* @param navigationIconContentColor the content color that will be applied via a
* [LocalContentColor] when composing the navigation icon
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index a49b358..4a7937a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -22,9 +22,11 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -92,6 +94,7 @@
)
},
containerColor = MaterialTheme.colorScheme.settingsBackground,
+ contentWindowInsets = WindowInsets.safeDrawing,
) { paddingValues ->
Box(
Modifier
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index af7a146..4cf741e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -23,7 +23,9 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -57,6 +59,7 @@
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
containerColor = MaterialTheme.colorScheme.settingsBackground,
+ contentWindowInsets = WindowInsets.safeDrawing,
) { paddingValues ->
Box(Modifier.padding(paddingValues.horizontalValues())) {
content(paddingValues.verticalValues())
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
index fc40930..4726dad 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -21,7 +21,9 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -55,7 +57,10 @@
content: @Composable () -> Unit,
) {
ActivityTitle(title)
- Scaffold(containerColor = MaterialTheme.colorScheme.settingsBackground) { innerPadding ->
+ Scaffold(
+ containerColor = MaterialTheme.colorScheme.settingsBackground,
+ contentWindowInsets = WindowInsets.safeDrawing,
+ ) { innerPadding ->
BoxWithConstraints(
Modifier
.padding(innerPadding)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
index 82ac7e3..f864fa9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -17,26 +17,17 @@
package com.android.settingslib.spa.widget.ui
import androidx.annotation.StringRes
-import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalUriHandler
-import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
import com.android.settingslib.spa.framework.util.annotatedStringResource
@Composable
fun AnnotatedText(@StringRes id: Int) {
- val uriHandler = LocalUriHandler.current
- val annotatedString = annotatedStringResource(id)
- ClickableText(
- text = annotatedString,
+ Text(
+ text = annotatedStringResource(id),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
- ) { offset ->
- // Gets the url at the clicked position.
- annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
- .firstOrNull()
- ?.let { uriHandler.openUri(it.item) }
- }
+ )
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index 9928355..612f9e5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -19,6 +19,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -36,18 +37,23 @@
val composeTestRule = createComposeRule()
@Test
- fun testAnnotatedStringResource() {
+ fun annotatedStringResource() {
composeTestRule.setContent {
val annotatedString =
annotatedStringResource(R.string.test_annotated_string_resource)
- val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
+ val annotations = annotatedString.getLinkAnnotations(0, annotatedString.length)
assertThat(annotations).containsExactly(
AnnotatedString.Range(
- item = "https://www.android.com/",
+ item = LinkAnnotation.Url(
+ url = "https://www.android.com/",
+ style = SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline,
+ ),
+ ),
start = 31,
end = 35,
- tag = URL_SPAN_TAG,
)
)
@@ -57,14 +63,6 @@
start = 22,
end = 26,
),
- AnnotatedString.Range(
- item = SpanStyle(
- color = MaterialTheme.colorScheme.primary,
- textDecoration = TextDecoration.Underline,
- ),
- start = 31,
- end = 35,
- ),
)
}
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 89f54d9..9c0d29d 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -62,3 +62,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "allow_all_widgets_on_lockscreen_by_default"
+ namespace: "systemui"
+ description: "Allow all widgets on the lock screen by default."
+ bug: "328261690"
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 57bde56..c365142 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -128,7 +128,7 @@
final long now = mClock.millis();
final UserManager um = mContext.getSystemService(UserManager.class);
final List<UserHandle> profiles = um.getUserProfiles();
- ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>();
+ ArrayMap<UserHandle, Boolean> shouldHideAppsByUsers = new ArrayMap<>();
for (int i = 0; i < appOpsCount; ++i) {
AppOpsManager.PackageOps ops = appOps.get(i);
@@ -136,13 +136,13 @@
int uid = ops.getUid();
UserHandle user = UserHandle.getUserHandleForUid(uid);
- if (!shouldIncludeAppsByUsers.containsKey(user)) {
- shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user));
+ if (!shouldHideAppsByUsers.containsKey(user)) {
+ shouldHideAppsByUsers.put(user, shouldHideUser(um, user));
}
// Don't show apps belonging to background users except for profiles that shouldn't
// be shown in quiet mode.
- if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) {
+ if (!profiles.contains(user) || shouldHideAppsByUsers.get(user)) {
continue;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 822a608..1040ac6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -36,6 +36,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.GuardedBy;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.SmsApplication;
@@ -56,13 +57,22 @@
private static PowerAllowlistBackend sInstance;
+ private final Object mAllowlistedAppsLock = new Object();
+ private final Object mSysAllowlistedAppsLock = new Object();
+ private final Object mDefaultActiveAppsLock = new Object();
+
private final Context mAppContext;
private final IDeviceIdleController mDeviceIdleService;
+
+ @GuardedBy("mAllowlistedAppsLock")
private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
+ @GuardedBy("mSysAllowlistedAppsLock")
private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
+ @GuardedBy("mDefaultActiveAppsLock")
private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
- public PowerAllowlistBackend(Context context) {
+ @VisibleForTesting
+ PowerAllowlistBackend(Context context) {
this(context, IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(DEVICE_IDLE_SERVICE)));
}
@@ -75,24 +85,25 @@
}
public int getAllowlistSize() {
- return mAllowlistedApps.size();
- }
-
- /**
- * Check if target package is in System allow list
- */
- public boolean isSysAllowlisted(String pkg) {
- return mSysAllowlistedApps.contains(pkg);
- }
-
- /**
- * Check if target package is in allow list
- */
- public boolean isAllowlisted(String pkg, int uid) {
- if (mAllowlistedApps.contains(pkg)) {
- return true;
+ synchronized (mAllowlistedAppsLock) {
+ return mAllowlistedApps.size();
}
+ }
+ /** Check if target package is in System allow list */
+ public boolean isSysAllowlisted(String pkg) {
+ synchronized (mSysAllowlistedAppsLock) {
+ return mSysAllowlistedApps.contains(pkg);
+ }
+ }
+
+ /** Check if target package is in allow list */
+ public boolean isAllowlisted(String pkg, int uid) {
+ synchronized (mAllowlistedAppsLock) {
+ if (mAllowlistedApps.contains(pkg)) {
+ return true;
+ }
+ }
if (isDefaultActiveApp(pkg, uid)) {
return true;
}
@@ -100,16 +111,16 @@
return false;
}
- /**
- * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
- */
+ /** Check if it is default active app in multiple area */
public boolean isDefaultActiveApp(String pkg, int uid) {
// Additionally, check if pkg is default dialer/sms. They are considered essential apps and
// should be automatically allowlisted (otherwise user may be able to set restriction on
// them, leading to bad device behavior.)
- if (mDefaultActiveApps.contains(pkg)) {
- return true;
+ synchronized (mDefaultActiveAppsLock) {
+ if (mDefaultActiveApps.contains(pkg)) {
+ return true;
+ }
}
final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(
@@ -143,9 +154,7 @@
DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED);
}
- /**
- * Check if target package is in allow list except idle app
- */
+ /** Check if target package is in allow list except idle app */
public boolean isAllowlistedExceptIdle(String pkg) {
try {
return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
@@ -156,6 +165,7 @@
}
/**
+ * Check if target package is in allow list except idle app
*
* @param pkgs a list of packageName
* @return true when one of package is in allow list
@@ -174,20 +184,21 @@
}
/**
- * Add app into power save allow list.
+ * Add app into power save allow list
+ *
* @param pkg packageName of the app
*/
- // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
addApp(pkg, Process.INVALID_UID);
}
/**
- * Add app into power save allow list.
+ * Add app into power save allow list
+ *
* @param pkg packageName of the app
* @param uid uid of the app
*/
- public void addApp(String pkg, int uid) {
+ public synchronized void addApp(String pkg, int uid) {
try {
if (android.app.Flags.appRestrictionsApi()) {
if (uid == Process.INVALID_UID) {
@@ -204,7 +215,9 @@
}
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
- mAllowlistedApps.add(pkg);
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.add(pkg);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
} catch (NameNotFoundException e) {
@@ -213,7 +226,8 @@
}
/**
- * Remove package from power save allow list.
+ * Remove package from power save allow list
+ *
* @param pkg packageName of the app
*/
public void removeApp(String pkg) {
@@ -222,10 +236,11 @@
/**
* Remove package from power save allow list.
+ *
* @param pkg packageName of the app
* @param uid uid of the app
*/
- public void removeApp(String pkg, int uid) {
+ public synchronized void removeApp(String pkg, int uid) {
try {
if (android.app.Flags.appRestrictionsApi()) {
if (uid == Process.INVALID_UID) {
@@ -241,7 +256,9 @@
}
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
- mAllowlistedApps.remove(pkg);
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.remove(pkg);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
} catch (NameNotFoundException e) {
@@ -249,25 +266,33 @@
}
}
- /**
- * Refresh all of lists
- */
+ /** Refresh all of lists */
@VisibleForTesting
- public void refreshList() {
- mSysAllowlistedApps.clear();
- mAllowlistedApps.clear();
- mDefaultActiveApps.clear();
+ public synchronized void refreshList() {
+ synchronized (mSysAllowlistedAppsLock) {
+ mSysAllowlistedApps.clear();
+ }
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.clear();
+ }
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.clear();
+ }
if (mDeviceIdleService == null) {
return;
}
try {
final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
- for (String app : allowlistedApps) {
- mAllowlistedApps.add(app);
+ synchronized (mAllowlistedAppsLock) {
+ for (String app : allowlistedApps) {
+ mAllowlistedApps.add(app);
+ }
}
final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
- for (String app : sysAllowlistedApps) {
- mSysAllowlistedApps.add(app);
+ synchronized (mSysAllowlistedAppsLock) {
+ for (String app : sysAllowlistedApps) {
+ mSysAllowlistedApps.add(app);
+ }
}
final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY);
@@ -278,26 +303,28 @@
if (hasTelephony) {
if (defaultSms != null) {
- mDefaultActiveApps.add(defaultSms.getPackageName());
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.add(defaultSms.getPackageName());
+ }
}
if (!TextUtils.isEmpty(defaultDialer)) {
- mDefaultActiveApps.add(defaultDialer);
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.add(defaultDialer);
+ }
}
}
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to invoke refreshList()", e);
}
}
- /**
- * @param context
- * @return a PowerAllowlistBackend object
- */
+ /** Get the {@link PowerAllowlistBackend} instance */
public static PowerAllowlistBackend getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new PowerAllowlistBackend(context);
+ synchronized (PowerAllowlistBackend.class) {
+ if (sInstance == null) {
+ sInstance = new PowerAllowlistBackend(context);
+ }
+ return sInstance;
}
- return sInstance;
}
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
new file mode 100644
index 0000000..d69c87b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.satellite
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.telephony.satellite.SatelliteManager
+import android.util.Log
+import android.view.WindowManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.wifi.WifiUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Default
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+import kotlin.coroutines.resume
+
+/** A util for Satellite dialog */
+object SatelliteDialogUtils {
+
+ /**
+ * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+ * Wifi during the satellite mode is on.
+ */
+ @JvmStatic
+ fun mayStartSatelliteWarningDialog(
+ context: Context,
+ lifecycleOwner: LifecycleOwner,
+ type: Int,
+ allowClick: (isAllowed: Boolean) -> Unit
+ ): Job {
+ return mayStartSatelliteWarningDialog(
+ context, lifecycleOwner.lifecycleScope, type, allowClick)
+ }
+
+ /**
+ * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+ * Wifi during the satellite mode is on.
+ */
+ @JvmStatic
+ fun mayStartSatelliteWarningDialog(
+ context: Context,
+ coroutineScope: CoroutineScope,
+ type: Int,
+ allowClick: (isAllowed: Boolean) -> Unit
+ ): Job =
+ coroutineScope.launch {
+ var isSatelliteModeOn = false
+ try {
+ isSatelliteModeOn = requestIsEnabled(context)
+ } catch (e: InterruptedException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ } catch (e: ExecutionException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ } catch (e: TimeoutException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ }
+
+ if (isSatelliteModeOn) {
+ startSatelliteWarningDialog(context, type)
+ }
+ withContext(Dispatchers.Main) {
+ allowClick(!isSatelliteModeOn)
+ }
+ }
+
+ private fun startSatelliteWarningDialog(context: Context, type: Int) {
+ context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+ component = ComponentName(
+ "com.android.settings",
+ "com.android.settings.network.SatelliteWarningDialogActivity"
+ )
+ putExtra(WifiUtils.DIALOG_WINDOW_TYPE,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ })
+ }
+
+ /**
+ * Checks if the satellite modem is enabled.
+ *
+ * @param executor The executor to run the asynchronous operation on
+ * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
+ * `false` otherwise.
+ */
+ private suspend fun requestIsEnabled(
+ context: Context,
+ ): Boolean = withContext(Default) {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return@withContext false
+ }
+
+ suspendCancellableCoroutine {continuation ->
+ satelliteManager?.requestIsEnabled(Default.asExecutor(),
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(result: Boolean) {
+ Log.i(TAG, "Satellite modem enabled status: $result")
+ continuation.resume(result)
+ }
+
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ super.onError(error)
+ Log.w(TAG, "Can't get satellite modem enabled status", error)
+ continuation.resume(false)
+ }
+ })
+ }
+ }
+
+ const val TAG = "SatelliteDialogUtils"
+
+ const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
+ "extra_type_of_satellite_warning_dialog"
+ const val TYPE_IS_UNKNOWN = -1
+ const val TYPE_IS_WIFI = 0
+ const val TYPE_IS_BLUETOOTH = 1
+ const val TYPE_IS_AIRPLANE_MODE = 2
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 2a44511..a939ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.statusbar.notification.data.repository
import android.app.NotificationManager
+import android.provider.Settings
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -28,10 +29,14 @@
override val notificationPolicy: StateFlow<NotificationManager.Policy?>
get() = mutableNotificationPolicy.asStateFlow()
- private val mutableZenMode = MutableStateFlow<ZenMode?>(null)
+ private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF))
override val zenMode: StateFlow<ZenMode?>
get() = mutableZenMode.asStateFlow()
+ init {
+ updateNotificationPolicy()
+ }
+
fun updateNotificationPolicy(policy: NotificationManager.Policy?) {
mutableNotificationPolicy.value = policy
}
@@ -48,13 +53,14 @@
suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
state: Int = NotificationManager.Policy.STATE_UNSET,
priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
-) = updateNotificationPolicy(
- NotificationManager.Policy(
- priorityCategories,
- priorityCallSenders,
- priorityMessageSenders,
- suppressedVisualEffects,
- state,
- priorityConversationSenders,
+) =
+ updateNotificationPolicy(
+ NotificationManager.Policy(
+ priorityCategories,
+ priorityCallSenders,
+ priorityMessageSenders,
+ suppressedVisualEffects,
+ state,
+ priorityConversationSenders,
+ )
)
-)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 65a5317..36e396fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -72,7 +72,11 @@
suspend fun setVolume(audioStream: AudioStream, volume: Int)
- suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+ /**
+ * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the
+ * otherwise.
+ */
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
@@ -164,14 +168,20 @@
audioManager.setStreamVolume(audioStream.value, volume, 0)
}
- override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
- withContext(backgroundCoroutineContext) {
- audioManager.adjustStreamVolume(
- audioStream.value,
- if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
- 0,
- )
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+ return withContext(backgroundCoroutineContext) {
+ if (isMuted == audioManager.isStreamMute(audioStream.value)) {
+ false
+ } else {
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
+ true
+ }
}
+ }
override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
deleted file mode 100644
index 1f037c0..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
+++ /dev/null
@@ -1,100 +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.settingslib.volume.data.repository
-
-import android.media.MediaMetadata
-import android.media.session.MediaController
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
-import android.os.Bundle
-import android.os.Handler
-import kotlinx.coroutines.channels.ProducerScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.launch
-
-/** [MediaController.Callback] flow representation. */
-fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> {
- return callbackFlow {
- val callback = MediaControllerCallbackProducer(this)
- registerCallback(callback, handler)
- awaitClose { unregisterCallback(callback) }
- }
-}
-
-/** Models particular change event received by [MediaController.Callback]. */
-sealed interface MediaControllerChange {
-
- data object SessionDestroyed : MediaControllerChange
-
- data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange
-
- data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange
-
- data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange
-
- data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
- MediaControllerChange
-
- data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange
-
- data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange
-
- data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange
-}
-
-private class MediaControllerCallbackProducer(
- private val producingScope: ProducerScope<MediaControllerChange>
-) : MediaController.Callback() {
-
- override fun onSessionDestroyed() {
- send(MediaControllerChange.SessionDestroyed)
- }
-
- override fun onSessionEvent(event: String, extras: Bundle?) {
- send(MediaControllerChange.SessionEvent(event, extras))
- }
-
- override fun onPlaybackStateChanged(state: PlaybackState?) {
- send(MediaControllerChange.PlaybackStateChanged(state))
- }
-
- override fun onMetadataChanged(metadata: MediaMetadata?) {
- send(MediaControllerChange.MetadataChanged(metadata))
- }
-
- override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
- send(MediaControllerChange.QueueChanged(queue))
- }
-
- override fun onQueueTitleChanged(title: CharSequence?) {
- send(MediaControllerChange.QueueTitleChanged(title))
- }
-
- override fun onExtrasChanged(extras: Bundle?) {
- send(MediaControllerChange.ExtrasChanged(extras))
- }
-
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
- send(MediaControllerChange.AudioInfoChanged(info))
- }
-
- private fun send(change: MediaControllerChange) {
- producingScope.launch { producingScope.send(change) }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 33f917e..0e5ebda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -25,6 +25,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -46,8 +47,16 @@
val ringerMode: StateFlow<RingerMode>
get() = audioRepository.ringerMode
- suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ val streamModel = getAudioStream(audioStream).first()
+ val oldVolume = streamModel.volume
audioRepository.setVolume(audioStream, volume)
+ when {
+ volume == streamModel.minVolume -> setMuted(audioStream, true)
+ oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
+ setMuted(audioStream, false)
+ }
+ }
suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
if (audioStream.value == AudioManager.STREAM_RING) {
@@ -55,7 +64,16 @@
if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
audioRepository.setRingerMode(audioStream, RingerMode(mode))
}
- audioRepository.setMuted(audioStream, isMuted)
+ val mutedChanged = audioRepository.setMuted(audioStream, isMuted)
+ if (mutedChanged && !isMuted) {
+ with(getAudioStream(audioStream).first()) {
+ if (volume == minVolume) {
+ // Slightly increase volume when user un-mutes the stream that is lowered
+ // down to its minimum
+ setVolume(audioStream, volume + 1)
+ }
+ }
+ }
}
/** Checks if the volume can be changed via the UI. */
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f4ddd0a..e125083 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -41,7 +41,10 @@
//###########################################################
android_robolectric_test {
name: "SettingsLibRoboTests",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
static_libs: [
"Settings_robolectric_meta_service_file",
"Robolectric_shadows_androidx_fragment_upstream",
@@ -51,6 +54,7 @@
"androidx.core_core",
"flag-junit",
"settingslib_media_flags_lib",
+ "settingslib_illustrationpreference_flags_lib",
"testng", // TODO: remove once JUnit on Android provides assertThrows
],
java_resource_dirs: ["config"],
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
new file mode 100644
index 0000000..aeda1ed6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.satellite
+
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.util.AndroidRuntimeException
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.verify
+import org.mockito.internal.verification.Times
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SatelliteDialogUtilsTest {
+ @JvmField
+ @Rule
+ val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ var context: Context = ApplicationProvider.getApplicationContext()
+ @Mock
+ private lateinit var satelliteManager: SatelliteManager
+
+ private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+ @Before
+ fun setUp() {
+ `when`(context.getSystemService(SatelliteManager::class.java))
+ .thenReturn(satelliteManager)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onResult(true)
+ null
+ }
+
+ try {
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertTrue(it)
+ })
+ } catch (e: AndroidRuntimeException) {
+ // Catch exception of starting activity .
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onResult(false)
+ null
+ }
+
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
+ `when`(context.getSystemService(SatelliteManager::class.java))
+ .thenReturn(null)
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
+ null
+ }
+
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 6590bbd..ca53fc2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -26,10 +26,14 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.net.Uri;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -39,11 +43,13 @@
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.widget.flags.Flags;
import com.android.settingslib.widget.preference.illustration.R;
import com.airbnb.lottie.LottieAnimationView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -51,10 +57,14 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import java.io.ByteArrayInputStream;
+
@RunWith(RobolectricTestRunner.class)
public class IllustrationPreferenceTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private ViewGroup mRootView;
private Uri mImageUri;
@@ -66,6 +76,7 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
private IllustrationPreference.OnBindListener mOnBindListener;
private LottieAnimationView mOnBindListenerAnimationView;
+ private FrameLayout mIllustrationFrame;
@Before
public void setUp() {
@@ -75,14 +86,14 @@
mBackgroundView = new ImageView(mContext);
mAnimationView = spy(new LottieAnimationView(mContext));
mMiddleGroundLayout = new FrameLayout(mContext);
- final FrameLayout illustrationFrame = new FrameLayout(mContext);
- illustrationFrame.setLayoutParams(
+ mIllustrationFrame = new FrameLayout(mContext);
+ mIllustrationFrame.setLayoutParams(
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
doReturn(mMiddleGroundLayout).when(mRootView).findViewById(R.id.middleground_layout);
doReturn(mBackgroundView).when(mRootView).findViewById(R.id.background_view);
doReturn(mAnimationView).when(mRootView).findViewById(R.id.lottie_view);
- doReturn(illustrationFrame).when(mRootView).findViewById(R.id.illustration_frame);
+ doReturn(mIllustrationFrame).when(mRootView).findViewById(R.id.illustration_frame);
mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView));
final AttributeSet attributeSet = Robolectric.buildAttributeSet().build();
@@ -158,11 +169,13 @@
}
@Test
+ @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
public void playLottieAnimationWithResource_verifyFailureListener() {
// fake the valid lottie image
final int fakeValidResId = 111;
doNothing().when(mAnimationView).setImageResource(fakeValidResId);
doReturn(null).when(mAnimationView).getDrawable();
+ doNothing().when(mAnimationView).setAnimation(fakeValidResId);
mPreference.setLottieAnimationResId(fakeValidResId);
mPreference.onBindViewHolder(mViewHolder);
@@ -171,6 +184,50 @@
}
@Test
+ @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+ public void handleImageWithAnimation_emptyInputStreamDisabledFlag_verifyContainerVisible() {
+ doNothing().when(mAnimationView).setImageResource(111);
+ doReturn(null).when(mAnimationView).getDrawable();
+
+ mPreference.setLottieAnimationResId(111);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+ public void handleImageWithAnimation_emptyInputStreamEnabledFlag_verifyContainerHidden() {
+ Resources res = spy(mContext.getResources());
+ doReturn(res).when(mAnimationView).getResources();
+ doReturn(new ByteArrayInputStream(new byte[] {})).when(res).openRawResource(111);
+
+ mPreference.setLottieAnimationResId(111);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mAnimationView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+ public void handleImageWithAnimation_nonEmptyInputStreamEnabledFlag_verifyContainerVisible() {
+ Resources res = spy(mContext.getResources());
+ doReturn(res).when(mAnimationView).getResources();
+ doReturn(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).when(res).openRawResource(111);
+ doNothing().when(mAnimationView).setImageResource(111);
+ doNothing().when(mAnimationView).setAnimation(111);
+ doReturn(null).when(mAnimationView).getDrawable();
+
+ mPreference.setLottieAnimationResId(111);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void setMaxHeight_smallerThanRestrictedHeight_matchResult() {
final int restrictedHeight =
mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index be3f410..888e395 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -274,6 +274,9 @@
Settings.Secure.SCREEN_RESOLUTION_MODE,
Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
- Settings.Secure.CHARGE_OPTIMIZATION_MODE
+ Settings.Secure.CHARGE_OPTIMIZATION_MODE,
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+ Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b1feede..b992ddc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -18,6 +18,7 @@
import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_LONG_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
@@ -433,5 +434,8 @@
VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
new InclusiveIntegerRangeValidator(0, 10));
VALIDATORS.put(Secure.CHARGE_OPTIMIZATION_MODE, new InclusiveIntegerRangeValidator(0, 10));
+ VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 677c81a..255b1ad 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -239,6 +239,18 @@
}
};
+ static final Validator ANY_LONG_VALIDATOR = value -> {
+ if (value == null) {
+ return true;
+ }
+ try {
+ Long.parseLong(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ };
+
static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
@Override
public boolean validate(String value) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 92167ee..ab9a30b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -233,6 +233,7 @@
Settings.Global.ENHANCED_4G_MODE_ENABLED,
Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+ Settings.Global.ERROR_KERNEL_LOG_PREFIX,
Settings.Global.ERROR_LOGCAT_PREFIX,
Settings.Global.EUICC_PROVISIONED,
Settings.Global.EUICC_SUPPORTED_COUNTRIES,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46bf494..374240b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -347,6 +347,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+ <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c04ec4f..d2ca112 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -78,11 +78,23 @@
visibility: ["//visibility:private"],
}
+filegroup {
+ name: "SystemUI-tests-broken-robofiles-run",
+ srcs: [
+ "tests/src/**/systemui/util/LifecycleFragmentTest.java",
+ "tests/src/**/systemui/util/TestableAlertDialogTest.kt",
+ "tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
+ "tests/src/**/systemui/util/sensors/AsyncManagerTest.java",
+ "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java",
+ "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java",
+ ],
+}
+
// We are running robolectric tests in the tests directory as well as
// multivalent tests. If you add a test, and it doesn't run in robolectric,
// it should be added to this exclusion list. go/multivalent-tests
filegroup {
- name: "SystemUI-tests-broken-robofiles",
+ name: "SystemUI-tests-broken-robofiles-compile",
srcs: [
"tests/src/**/*DeviceOnlyTest.java",
"tests/src/**/*DeviceOnlyTest.kt",
@@ -703,7 +715,8 @@
":SystemUI-tests-robofiles",
],
exclude_srcs: [
- ":SystemUI-tests-broken-robofiles",
+ ":SystemUI-tests-broken-robofiles-compile",
+ ":SystemUI-tests-broken-robofiles-run",
],
static_libs: [
"RoboTestLibraries",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 626e219..10ba7bd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -34,6 +34,13 @@
}
flag {
+ name: "priority_people_section"
+ namespace: "systemui"
+ description: "Add a new section for priority people (aka important conversations)."
+ bug: "340294566"
+}
+
+flag {
name: "notification_minimalism_prototype"
namespace: "systemui"
description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
@@ -197,7 +204,16 @@
description: "Re-enable the codepath that removed tinting of notifications when the"
" standard background color is desired. This was the behavior before we discovered"
" a resources threading issue, which we worked around by tinting the notification"
- " backgrounds and footer buttons."
+ " backgrounds."
+ bug: "294830092"
+}
+
+flag {
+ name: "notification_footer_background_tint_optimization"
+ namespace: "systemui"
+ description: "Remove duplicative tinting of notification footer buttons. This was the behavior"
+ " before we discovered a resources threading issue, which we worked around by applying the"
+ " same color as a tint to the background drawable of footer buttons."
bug: "294830092"
}
@@ -348,6 +364,14 @@
}
flag {
+ name: "status_bar_screen_sharing_chips"
+ namespace: "systemui"
+ description: "Show chips on the left side of the status bar when a user is screen sharing, "
+ "recording, or casting"
+ bug: "332662551"
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -398,6 +422,16 @@
}
flag {
+ name: "fix_image_wallpaper_crash_surface_already_released"
+ namespace: "systemui"
+ description: "Make sure ImageWallpaper doesn't return from OnSurfaceDestroyed until any drawing is finished"
+ bug: "337287154"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "activity_transition_use_largest_window"
namespace: "systemui"
description: "Target largest opening window during activity transitions."
@@ -470,6 +504,26 @@
}
flag {
+ name: "fix_screenshot_action_dismiss_system_windows"
+ namespace: "systemui"
+ description: "Dismiss existing system windows when starting action from screenshot UI"
+ bug: "309933761"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "screenshot_scroll_crop_view_crash_fix"
+ namespace: "systemui"
+ description: "Mitigate crash on invalid computed range in CropView"
+ bug: "232633995"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "screenshot_private_profile_behavior_fix"
namespace: "systemui"
description: "Private profile support for screenshots"
@@ -844,6 +898,9 @@
namespace: "systemui"
description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS."
bug: "329205638"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -891,4 +948,31 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "media_controls_user_initiated_dismiss"
+ namespace: "systemui"
+ description: "Only dismiss media notifications when the control was removed by the user."
+ bug: "335875159"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "validate_keyboard_shortcut_helper_icon_uri"
+ namespace: "systemui"
+ description: "Adds a check that the caller can access the content URI of an icon in the shortcut helper."
+ bug: "331180422"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "glanceable_hub_gesture_handle"
+ namespace: "systemui"
+ description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
+ bug: "339667383"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index d4660fa..23df26f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -23,6 +23,7 @@
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Binder
import android.os.Build
import android.os.Handler
import android.os.Looper
@@ -36,7 +37,11 @@
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.animation.PathInterpolator
+import android.window.RemoteTransition
+import android.window.TransitionFilter
import androidx.annotation.AnyThread
import androidx.annotation.BinderThread
import androidx.annotation.UiThread
@@ -44,6 +49,9 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.wm.shell.shared.IShellTransitions
+import com.android.wm.shell.shared.ShellTransitions
import java.util.concurrent.Executor
import kotlin.math.roundToInt
@@ -59,6 +67,9 @@
/** The executor that runs on the main thread. */
private val mainExecutor: Executor,
+ /** The object used to register ephemeral returns and long-lived transitions. */
+ private val transitionRegister: TransitionRegister? = null,
+
/** The animator used when animating a View into an app. */
private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
@@ -74,6 +85,36 @@
// TODO(b/301385865): Remove this flag.
private val disableWmTimeout: Boolean = false,
) {
+ @JvmOverloads
+ constructor(
+ mainExecutor: Executor,
+ shellTransitions: ShellTransitions,
+ transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+ dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+ disableWmTimeout: Boolean = false,
+ ) : this(
+ mainExecutor,
+ TransitionRegister.fromShellTransitions(shellTransitions),
+ transitionAnimator,
+ dialogToAppAnimator,
+ disableWmTimeout,
+ )
+
+ @JvmOverloads
+ constructor(
+ mainExecutor: Executor,
+ iShellTransitions: IShellTransitions,
+ transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+ dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+ disableWmTimeout: Boolean = false,
+ ) : this(
+ mainExecutor,
+ TransitionRegister.fromIShellTransitions(iShellTransitions),
+ transitionAnimator,
+ dialogToAppAnimator,
+ disableWmTimeout,
+ )
+
companion object {
/** The timings when animating a View into an app. */
@JvmField
@@ -233,6 +274,10 @@
}
}
+ if (animationAdapter != null && controller.transitionCookie != null) {
+ registerEphemeralReturnAnimation(controller, transitionRegister)
+ }
+
val launchResult = intentStarter(animationAdapter)
// Only animate if the app is not already on top and will be opened, unless we are on the
@@ -302,6 +347,66 @@
}
}
+ /**
+ * Uses [transitionRegister] to set up the return animation for the given [launchController].
+ *
+ * De-registration is set up automatically once the return animation is run.
+ *
+ * TODO(b/339194555): automatically de-register when the launchable is detached.
+ */
+ private fun registerEphemeralReturnAnimation(
+ launchController: Controller,
+ transitionRegister: TransitionRegister?
+ ) {
+ if (!returnAnimationFrameworkLibrary()) return
+
+ var cleanUpRunnable: Runnable? = null
+ val returnRunner =
+ createRunner(
+ object : DelegateTransitionAnimatorController(launchController) {
+ override val isLaunching = false
+
+ override fun onTransitionAnimationCancelled(
+ newKeyguardOccludedState: Boolean?
+ ) {
+ super.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ cleanUp()
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ super.onTransitionAnimationEnd(isExpandingFullyAbove)
+ cleanUp()
+ }
+
+ private fun cleanUp() {
+ cleanUpRunnable?.run()
+ }
+ }
+ )
+
+ // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
+ // we only want ephemeral return animations triggered in these scenarios.
+ val filter =
+ TransitionFilter().apply {
+ mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+ mRequirements =
+ arrayOf(
+ TransitionFilter.Requirement().apply {
+ mLaunchCookie = launchController.transitionCookie
+ mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+ }
+ )
+ }
+ val transition =
+ RemoteTransition(
+ RemoteAnimationRunnerCompat.wrap(returnRunner),
+ "${launchController.transitionCookie}_returnTransition"
+ )
+
+ transitionRegister?.register(filter, transition)
+ cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
+ }
+
/** Add a [Listener] that can listen to transition animations. */
fun addListener(listener: Listener) {
listeners.add(listener)
@@ -386,8 +491,14 @@
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
+ @JvmOverloads
@JvmStatic
- fun fromView(view: View, cujType: Int? = null): Controller? {
+ fun fromView(
+ view: View,
+ cujType: Int? = null,
+ cookie: TransitionCookie? = null,
+ returnCujType: Int? = null
+ ): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues.
if (view !is LaunchableView) {
@@ -408,7 +519,7 @@
return null
}
- return GhostedViewTransitionAnimatorController(view, cujType)
+ return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType)
}
}
@@ -432,6 +543,17 @@
get() = false
/**
+ * The cookie associated with the transition controlled by this [Controller].
+ *
+ * This should be defined for all return [Controller] (when [isLaunching] is false) and for
+ * their associated launch [Controller]s.
+ *
+ * For the recommended format, see [TransitionCookie].
+ */
+ val transitionCookie: TransitionCookie?
+ get() = null
+
+ /**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
@@ -652,7 +774,7 @@
return
}
- val window = findRootTaskIfPossible(apps)
+ val window = findTargetWindowIfPossible(apps)
if (window == null) {
Log.i(TAG, "Aborting the animation as no window is opening")
callback?.invoke()
@@ -676,7 +798,7 @@
startAnimation(window, navigationBar, callback)
}
- private fun findRootTaskIfPossible(
+ private fun findTargetWindowIfPossible(
apps: Array<out RemoteAnimationTarget>?
): RemoteAnimationTarget? {
if (apps == null) {
@@ -694,6 +816,19 @@
for (it in apps) {
if (it.mode == targetMode) {
if (activityTransitionUseLargestWindow()) {
+ if (returnAnimationFrameworkLibrary()) {
+ // If the controller contains a cookie, _only_ match if the candidate
+ // contains the matching cookie.
+ if (
+ controller.transitionCookie != null &&
+ it.taskInfo
+ ?.launchCookies
+ ?.contains(controller.transitionCookie) != true
+ ) {
+ continue
+ }
+ }
+
if (
candidate == null ||
!it.hasAnimatingParent && candidate.hasAnimatingParent
@@ -806,11 +941,7 @@
progress: Float,
linearProgress: Float
) {
- // Apply the state to the window only if it is visible, i.e. when the
- // expanding view is *not* visible.
- if (!state.visible) {
- applyStateToWindow(window, state, linearProgress)
- }
+ applyStateToWindow(window, state, linearProgress)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
listener?.onTransitionAnimationProgress(linearProgress)
@@ -1048,4 +1179,72 @@
return (this.width() * this.height()) > (other.width() * other.height())
}
}
+
+ /**
+ * Wraps one of the two methods we have to register remote transitions with WM Shell:
+ * - for in-process registrations (e.g. System UI) we use [ShellTransitions]
+ * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions]
+ *
+ * Important: each instance of this class must wrap exactly one of the two.
+ */
+ class TransitionRegister
+ private constructor(
+ private val shellTransitions: ShellTransitions? = null,
+ private val iShellTransitions: IShellTransitions? = null,
+ ) {
+ init {
+ assert((shellTransitions != null).xor(iShellTransitions != null))
+ }
+
+ companion object {
+ /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */
+ fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister {
+ return TransitionRegister(shellTransitions = shellTransitions)
+ }
+
+ /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */
+ fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister {
+ return TransitionRegister(iShellTransitions = iShellTransitions)
+ }
+ }
+
+ /** Register [remoteTransition] with WM Shell using the given [filter]. */
+ internal fun register(
+ filter: TransitionFilter,
+ remoteTransition: RemoteTransition,
+ ) {
+ shellTransitions?.registerRemote(filter, remoteTransition)
+ iShellTransitions?.registerRemote(filter, remoteTransition)
+ }
+
+ /** Unregister [remoteTransition] from WM Shell. */
+ internal fun unregister(remoteTransition: RemoteTransition) {
+ shellTransitions?.unregisterRemote(remoteTransition)
+ iShellTransitions?.unregisterRemote(remoteTransition)
+ }
+ }
+
+ /**
+ * A cookie used to uniquely identify a task launched using an
+ * [ActivityTransitionAnimator.Controller].
+ *
+ * The [String] encapsulated by this class should be formatted in such a way to be unique across
+ * the system, but reliably constant for the same associated launchable.
+ *
+ * Recommended naming scheme:
+ * - DO use the fully qualified name of the class that owns the instance of the launchable,
+ * along with a concise and precise description of the purpose of the launchable in question.
+ * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that
+ * will change if the instance is destroyed and re-created.
+ *
+ * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton"
+ *
+ * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same
+ * launchable, and no static knowledge to adequately differentiate between them using a single
+ * description. In this case, the recommendation is to append a unique identifier related to the
+ * contents of the launchable.
+ *
+ * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256”
+ */
+ data class TransitionCookie(private val cookie: String) : Binder()
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index e4bb2ad..21557b8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -25,10 +25,30 @@
* [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated
* (e.g. if it is currently not attached or visible).
*
- * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+ * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
* associated to the launch that will use this controller.
+ * @param cookie The unique cookie associated with the launch that will use this controller.
+ * This is required iff the a return animation should be included.
+ * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+ * associated to the return animation that will use this controller.
*/
- fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller?
+ fun activityTransitionController(
+ launchCujType: Int? = null,
+ cookie: ActivityTransitionAnimator.TransitionCookie? = null,
+ returnCujType: Int? = null
+ ): ActivityTransitionAnimator.Controller?
+
+ /**
+ * See [activityTransitionController] above.
+ *
+ * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that
+ * don't use the return-related parameters.
+ */
+ fun activityTransitionController(
+ launchCujType: Int? = null
+ ): ActivityTransitionAnimator.Controller? {
+ return activityTransitionController(launchCujType, cookie = null, returnCujType = null)
+ }
/**
* Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable]
@@ -48,9 +68,16 @@
fun fromView(view: View): Expandable {
return object : Expandable {
override fun activityTransitionController(
- cujType: Int?,
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
- return ActivityTransitionAnimator.Controller.fromView(view, cujType)
+ return ActivityTransitionAnimator.Controller.fromView(
+ view,
+ launchCujType,
+ cookie,
+ returnCujType
+ )
}
override fun dialogTransitionController(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index fd79f62..9d45073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -59,8 +59,12 @@
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
- /** The [CujType] associated to this animation. */
- private val cujType: Int? = null,
+ /** The [CujType] associated to this launch animation. */
+ private val launchCujType: Int? = null,
+ override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
+
+ /** The [CujType] associated to this return animation. */
+ private val returnCujType: Int? = null,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
@@ -104,6 +108,15 @@
*/
private val background: Drawable?
+ /** CUJ identifier accounting for whether this controller is for a launch or a return. */
+ private val cujType: Int?
+ get() =
+ if (isLaunching) {
+ launchCujType
+ } else {
+ returnCujType
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
if (ghostedView !is LaunchableView) {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt
new file mode 100644
index 0000000..94620c4
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+class CollectAsStateDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes(): List<Class<out UElement>> {
+ return listOf(UFile::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ node.imports.forEach { importStatement ->
+ visitImportStatement(context, importStatement)
+ }
+ }
+ }
+ }
+
+ private fun visitImportStatement(
+ context: JavaContext,
+ importStatement: UImportStatement,
+ ) {
+ val importText = importStatement.importReference?.asSourceString() ?: return
+ if (ILLEGAL_IMPORT == importText) {
+ context.report(
+ issue = ISSUE,
+ scope = importStatement,
+ location = context.getLocation(importStatement),
+ message = "collectAsState considered harmful",
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE =
+ Issue.create(
+ id = "OverlyEagerCollectAsState",
+ briefDescription = "collectAsState considered harmful",
+ explanation =
+ """
+ go/sysui-compose#collect-as-state
+
+ Don't use collectAsState as it will set up a coroutine that keeps collecting from a
+ flow until its coroutine scope becomes inactive. This prevents the work from being
+ properly paused while the surrounding lifecycle becomes paused or stopped and is
+ therefore considered harmful.
+
+ Instead, use Flow.collectAsStateWithLifecycle(initial: T) or
+ StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection
+ coroutine while the lifecycle drops below the specified minActiveState (which
+ defaults to STARTED meaning that it will pause when the Compose-hosting window
+ becomes invisible).
+ """
+ .trimIndent(),
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.ERROR,
+ implementation =
+ Implementation(
+ CollectAsStateDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+
+ private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState"
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index cecbc47..73ac6cc 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -32,6 +32,7 @@
BindServiceOnMainThreadDetector.ISSUE,
BroadcastSentViaContextDetector.ISSUE,
CleanArchitectureDependencyViolationDetector.ISSUE,
+ CollectAsStateDetector.ISSUE,
DumpableNotRegisteredDetector.ISSUE,
FlowDetector.SHARED_FLOW_CREATION,
SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt
new file mode 100644
index 0000000..6962b4e
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class CollectAsStateDetectorTest : SystemUILintDetectorTest() {
+
+ override fun getDetector(): Detector {
+ return CollectAsStateDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ CollectAsStateDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun testViolation() {
+ lint()
+ .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE, BAD_FILE)
+ .issues(CollectAsStateDetector.ISSUE)
+ .run()
+ .expect(
+ """
+src/com/android/internal/systemui/lint/Bad.kt:3: Error: collectAsState considered harmful [OverlyEagerCollectAsState]
+import androidx.compose.runtime.collectAsState
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+1 errors, 0 warnings
+ """
+ .trimIndent()
+ )
+ }
+
+ @Test
+ fun testNoViolation() {
+ lint()
+ .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE)
+ .issues(CollectAsStateDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ companion object {
+ private val COLLECT_AS_STATE_STUB =
+ TestFiles.kotlin(
+ """
+ package androidx.compose.runtime
+
+ fun collectAsState() {}
+ """
+ .trimIndent()
+ )
+ private val COLLECT_WITH_LIFECYCLE_AS_STATE_STUB =
+ TestFiles.kotlin(
+ """
+ package androidx.lifecycle.compose
+
+ fun collectAsStateWithLifecycle() {}
+ """
+ .trimIndent()
+ )
+
+ private val BAD_FILE =
+ TestFiles.kotlin(
+ """
+ package com.android.internal.systemui.lint
+
+ import androidx.compose.runtime.collectAsState
+
+ class Bad
+ """
+ .trimIndent()
+ )
+
+ private val GOOD_FILE =
+ TestFiles.kotlin(
+ """
+ package com.android.internal.systemui.lint
+
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
+
+ class Good
+ """
+ .trimIndent()
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index c7f0a96..17a6061 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -134,13 +134,15 @@
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
- cujType: Int?,
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(cujType)
+ return activityController(launchCujType, cookie, returnCujType)
}
override fun dialogTransitionController(
@@ -262,10 +264,27 @@
}
/** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */
- private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller {
+ private fun activityController(
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
+ ): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
return object :
ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate {
+ /**
+ * CUJ identifier accounting for whether this controller is for a launch or a return.
+ */
+ private val cujType: Int?
+ get() =
+ if (isLaunching) {
+ launchCujType
+ } else {
+ returnCujType
+ }
+
+ override val transitionCookie = cookie
+
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index c22b50d..fa01a4b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -56,7 +56,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -77,6 +76,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformButton
import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ElementKey
@@ -111,7 +111,7 @@
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
) {
- val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
+ val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle()
val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
Box(
@@ -220,7 +220,7 @@
viewModel: BouncerViewModel,
modifier: Modifier = Modifier,
) {
- val authMethod by viewModel.authMethodViewModel.collectAsState()
+ val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
Row(
modifier =
@@ -316,7 +316,7 @@
val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
val isHeightExpanded =
LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
- val authMethod by viewModel.authMethodViewModel.collectAsState()
+ val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
Row(
modifier =
@@ -480,7 +480,7 @@
modifier: Modifier = Modifier,
) {
val foldPosture: FoldPosture by foldPosture()
- val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
+ val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsStateWithLifecycle()
val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
@@ -562,7 +562,7 @@
viewModel: BouncerMessageViewModel,
modifier: Modifier = Modifier,
) {
- val message: MessageViewModel? by viewModel.message.collectAsState()
+ val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -612,7 +612,7 @@
modifier: Modifier = Modifier,
) {
val authMethodViewModel: AuthMethodBouncerViewModel? by
- viewModel.authMethodViewModel.collectAsState()
+ viewModel.authMethodViewModel.collectAsStateWithLifecycle()
when (val nonNullViewModel = authMethodViewModel) {
is PinBouncerViewModel ->
@@ -642,7 +642,7 @@
modifier: Modifier = Modifier,
) {
val authMethodViewModel: AuthMethodBouncerViewModel? by
- viewModel.authMethodViewModel.collectAsState()
+ viewModel.authMethodViewModel.collectAsStateWithLifecycle()
when (val nonNullViewModel = authMethodViewModel) {
is PinBouncerViewModel -> {
@@ -668,7 +668,8 @@
viewModel: BouncerViewModel,
modifier: Modifier = Modifier,
) {
- val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+ val actionButton: BouncerActionButtonModel? by
+ viewModel.actionButton.collectAsStateWithLifecycle()
val appearFadeInAnimatable = remember { Animatable(0f) }
val appearMoveAnimatable = remember { Animatable(0f) }
val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() }
@@ -735,7 +736,7 @@
bouncerViewModel: BouncerViewModel,
dialogFactory: BouncerDialogFactory,
) {
- val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsState()
+ val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle()
var dialog: AlertDialog? by remember { mutableStateOf(null) }
dialogViewModel?.let { viewModel ->
@@ -772,8 +773,8 @@
return
}
- val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
- val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
+ val selectedUserImage by viewModel.selectedUserImage.collectAsStateWithLifecycle(null)
+ val dropdownItems by viewModel.userSwitcherDropdown.collectAsStateWithLifecycle(emptyList())
Column(
horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2dcd0ff..203bd7a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -27,7 +27,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -49,6 +48,7 @@
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
@@ -62,18 +62,20 @@
modifier: Modifier = Modifier,
) {
val focusRequester = remember { FocusRequester() }
- val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState()
+ val isTextFieldFocusRequested by
+ viewModel.isTextFieldFocusRequested.collectAsStateWithLifecycle()
LaunchedEffect(isTextFieldFocusRequested) {
if (isTextFieldFocusRequested) {
focusRequester.requestFocus()
}
}
- val password: String by viewModel.password.collectAsState()
- val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
- val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
- val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
- val selectedUserId by viewModel.selectedUserId.collectAsState()
+ val password: String by viewModel.password.collectAsStateWithLifecycle()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
+ val isImeSwitcherButtonVisible by
+ viewModel.isImeSwitcherButtonVisible.collectAsStateWithLifecycle()
+ val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle()
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index d7e9c10..9c2fd64 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -30,7 +30,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -47,6 +46,7 @@
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.integerResource
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Easings
import com.android.compose.modifiers.thenIf
import com.android.internal.R
@@ -86,14 +86,15 @@
val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() }
// All dots that should be rendered on the grid.
- val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
+ val dots: List<PatternDotViewModel> by viewModel.dots.collectAsStateWithLifecycle()
// The most recently selected dot, if the user is currently dragging.
- val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState()
+ val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsStateWithLifecycle()
// The dots selected so far, if the user is currently dragging.
- val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
- val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
- val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState()
- val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+ val selectedDots: List<PatternDotViewModel> by
+ viewModel.selectedDots.collectAsStateWithLifecycle()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+ val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsStateWithLifecycle()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
// Map of animatables for the scale of each dot, keyed by dot.
val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 5651a46..64ace2f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -33,7 +33,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -49,6 +48,7 @@
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Easings
import com.android.compose.grid.VerticalGrid
import com.android.compose.modifiers.thenIf
@@ -74,12 +74,13 @@
) {
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
- val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
- val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
- val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
- val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+ val backspaceButtonAppearance by
+ viewModel.backspaceButtonAppearance.collectAsStateWithLifecycle()
+ val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsStateWithLifecycle()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
val isDigitButtonAnimationEnabled: Boolean by
- viewModel.isDigitButtonAnimationEnabled.collectAsState()
+ viewModel.isDigitButtonAnimationEnabled.collectAsStateWithLifecycle()
val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } }
LaunchedEffect(animateFailure) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 1a97912..465eade 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -42,7 +42,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf
@@ -65,6 +64,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformOutlinedButton
import com.android.compose.animation.Easings
import com.android.keyguard.PinShapeAdapter
@@ -86,7 +86,7 @@
viewModel: PinBouncerViewModel,
modifier: Modifier = Modifier,
) {
- val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState()
+ val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsStateWithLifecycle()
val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
// The display comes in two different flavors:
@@ -119,7 +119,7 @@
hintedPinLength: Int,
modifier: Modifier = Modifier,
) {
- val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState()
+ val pinInput: PinInputViewModel by viewModel.pinInput.collectAsStateWithLifecycle()
// [ClearAll] marker pointing at the beginning of the current pin input.
// When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played
// and the marker is advanced manually to the most recent marker. See LaunchedEffect below.
@@ -257,9 +257,10 @@
@Composable
private fun SimArea(viewModel: PinBouncerViewModel) {
- val isLockedEsim by viewModel.isLockedEsim.collectAsState()
- val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState()
- val errorDialogMessage by viewModel.errorDialogMessage.collectAsState()
+ val isLockedEsim by viewModel.isLockedEsim.collectAsStateWithLifecycle()
+ val isSimUnlockingDialogVisible by
+ viewModel.isSimUnlockingDialogVisible.collectAsStateWithLifecycle()
+ val errorDialogMessage by viewModel.errorDialogMessage.collectAsStateWithLifecycle()
var unlockDialog: Dialog? by remember { mutableStateOf(null) }
var errorDialog: Dialog? by remember { mutableStateOf(null) }
val context = LocalView.current.context
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
index c8e1450..694326d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -35,7 +35,7 @@
* ```
* @Composable
* fun YourFunction(viewModel: YourViewModel) {
- * val selectedUserId by viewModel.selectedUserId.collectAsState()
+ * val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle()
*
* SelectedUserAwareInputConnection(selectedUserId) {
* TextField(...)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 8144d15..296fc27 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -22,12 +22,12 @@
import androidx.compose.foundation.layout.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.StateFlow
/** The bounds and [CutoutLocation] of the current display. */
@@ -45,7 +45,7 @@
screenCornerRadius: Float,
content: @Composable () -> Unit,
) {
- val cutout by displayCutout.collectAsState()
+ val cutout by displayCutout.collectAsStateWithLifecycle()
val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
val density = LocalDensity.current
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 08e452c..feb1f5b 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
@@ -2,17 +2,25 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -26,6 +34,7 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
+import com.android.systemui.Flags
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
@@ -85,8 +94,11 @@
content: CommunalContent,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
- val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+ val currentSceneKey: SceneKey by
+ viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
+ val showGestureIndicator by
+ viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -125,7 +137,19 @@
)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
- Box(modifier = Modifier.fillMaxSize())
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) {
+ if (showGestureIndicator && Flags.glanceableHubGestureHandle()) {
+ Box(
+ modifier =
+ Modifier.height(220.dp)
+ .width(4.dp)
+ .align(Alignment.CenterVertically)
+ .background(color = Color.White, RoundedCornerShape(4.dp))
+ )
+ Spacer(modifier = Modifier.width(12.dp))
+ }
+ }
}
scene(
@@ -149,7 +173,8 @@
content: CommunalContent,
modifier: Modifier = Modifier,
) {
- val backgroundColor by colors.backgroundColor.collectAsState()
+ val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
+
Box(
modifier =
Modifier.element(Communal.Elements.Scrim)
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 02621f6..cd27d57 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
@@ -48,6 +48,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
@@ -78,7 +79,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -88,8 +88,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
@@ -121,9 +119,11 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.times
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
-import androidx.core.view.setPadding
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
@@ -156,20 +156,21 @@
onOpenWidgetPicker: (() -> Unit)? = null,
onEditDone: (() -> Unit)? = null,
) {
- val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
- val currentPopup by viewModel.currentPopup.collectAsState(initial = null)
+ val communalContent by
+ viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
+ val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var isDraggingToRemove by remember { mutableStateOf(false) }
val gridState = rememberLazyGridState()
val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
- val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
- val selectedKey = viewModel.selectedKey.collectAsState()
+ val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle()
+ val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle()
val removeButtonEnabled by remember {
derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
- val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false)
+ val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false)
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -303,9 +304,9 @@
if (viewModel is CommunalViewModel && dialogFactory != null) {
val isEnableWidgetDialogShowing by
- viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+ viewModel.isEnableWidgetDialogShowing.collectAsStateWithLifecycle(false)
val isEnableWorkProfileDialogShowing by
- viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+ viewModel.isEnableWorkProfileDialogShowing.collectAsStateWithLifecycle(false)
EnableWidgetDialog(
isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
@@ -426,8 +427,8 @@
state = gridState,
rows = GridCells.Fixed(CommunalContentSize.FULL.span),
contentPadding = contentPadding,
- horizontalArrangement = Arrangement.spacedBy(32.dp),
- verticalArrangement = Arrangement.spacedBy(32.dp),
+ horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+ verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
) {
items(
count = list.size,
@@ -440,7 +441,7 @@
Dimensions.CardWidth.value,
list[index].size.dp().value,
)
- val cardModifier = Modifier.size(width = size.width.dp, height = size.height.dp)
+ val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
if (viewModel.isEditMode && dragDropState != null) {
val selected by
remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
@@ -794,12 +795,10 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+ shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
- verticalArrangement =
- Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
@@ -807,11 +806,13 @@
contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
modifier = Modifier.size(Dimensions.IconSize),
)
+ Spacer(modifier = Modifier.size(6.dp))
Text(
text = stringResource(R.string.cta_label_to_edit_widget),
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
)
+ Spacer(modifier = Modifier.size(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
@@ -827,9 +828,10 @@
) {
Text(
text = stringResource(R.string.cta_tile_button_to_dismiss),
+ fontSize = 12.sp,
)
}
- Spacer(modifier = Modifier.size(Dimensions.Spacing))
+ Spacer(modifier = Modifier.size(14.dp))
Button(
colors =
ButtonDefaults.buttonColors(
@@ -841,6 +843,7 @@
) {
Text(
text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ fontSize = 12.sp,
)
}
}
@@ -860,7 +863,7 @@
contentListState: ContentListState,
) {
val context = LocalContext.current
- val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+ val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
val accessibilityLabel =
remember(model, context) {
model.providerInfo.loadLabel(context.packageManager).toString().trim()
@@ -868,7 +871,7 @@
val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget)
val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
- val selectedKey by viewModel.selectedKey.collectAsState()
+ val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
Box(
@@ -926,10 +929,14 @@
model.appWidgetHost
.createViewForCommunal(context, model.appWidgetId, model.providerInfo)
.apply {
- updateAppWidgetSize(Bundle.EMPTY, listOf(size))
- // Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box.
- setPadding(0)
+ updateAppWidgetSize(
+ /* newOptions = */ Bundle(),
+ /* minWidth = */ size.width.toInt(),
+ /* minHeight = */ size.height.toInt(),
+ /* maxWidth = */ size.width.toInt(),
+ /* maxHeight = */ size.height.toInt(),
+ /* ignorePadding = */ true
+ )
accessibilityDelegate = viewModel.widgetAccessibilityDelegate
}
},
@@ -1109,7 +1116,7 @@
@Composable
fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) {
val context = LocalContext.current
- val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+ val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
Box(
modifier =
Modifier.fillMaxWidth().wrapContentHeight().thenIf(
@@ -1152,7 +1159,11 @@
@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
if (!isEditMode || toolbarSize == null) {
- return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
+ return PaddingValues(
+ start = Dimensions.ItemSpacing,
+ end = Dimensions.ItemSpacing,
+ top = Dimensions.GridTopSpacing,
+ )
}
val context = LocalContext.current
val density = LocalDensity.current
@@ -1215,18 +1226,19 @@
}
object Dimensions {
- val CardWidth = 424.dp
- val CardHeightFull = 596.dp
- val CardHeightHalf = 282.dp
- val CardHeightThird = 177.33.dp
- val CardOutlineWidth = 3.dp
- val GridTopSpacing = 64.dp
+ val CardHeightFull = 530.dp
+ val GridTopSpacing = 114.dp
val GridHeight = CardHeightFull + GridTopSpacing
- val Spacing = 16.dp
+ val ItemSpacing = 50.dp
+ val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+ val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+ val CardWidth = 360.dp
+ val CardOutlineWidth = 3.dp
+ val Spacing = ItemSpacing / 2
// The sizing/padding of the toolbar in glanceable hub edit mode
val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = 16.dp
+ val ToolbarPaddingHorizontal = ItemSpacing
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
val ButtonPadding =
@@ -1234,10 +1246,7 @@
vertical = ToolbarButtonPaddingVertical,
horizontal = ToolbarButtonPaddingHorizontal,
)
- val IconSize = 48.dp
-
- val LockIconSize = 52.dp
- val LockIconBottomPadding = 70.dp
+ val IconSize = 40.dp
}
private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
index e77ade9..17dac7e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
@@ -18,11 +18,11 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowInfoTracker
import com.android.systemui.fold.ui.helper.FoldPosture
import com.android.systemui.fold.ui.helper.foldPostureInternal
@@ -32,7 +32,8 @@
fun foldPosture(): State<FoldPosture> {
val context = LocalContext.current
val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) }
- val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null)
+ val layoutInfo by
+ infoTracker.windowLayoutInfo(context).collectAsStateWithLifecycle(initialValue = null)
return produceState<FoldPosture>(
initialValue = FoldPosture.Folded,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index a8d801a..67840c7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -29,7 +29,6 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
@@ -37,6 +36,7 @@
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
@@ -57,7 +57,7 @@
@Composable
fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
- val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
+ val stickyKeys by viewModel.indicatorContent.collectAsStateWithLifecycle(emptyMap())
StickyKeysIndicator(stickyKeys)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 4bef9ef..6d8c47d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,11 +18,11 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
@@ -51,7 +51,7 @@
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+ val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
val view = LocalView.current
DisposableEffect(view) {
clockInteractor.clockEventController.registerListeners(view)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
index 472484a..4555f13 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -26,13 +26,13 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
/** Container for lockscreen content that handles long-press to bring up the settings menu. */
@@ -42,7 +42,8 @@
modifier: Modifier = Modifier,
content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
) {
- val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+ val isEnabled: Boolean by
+ viewModel.isLongPressHandlingEnabled.collectAsStateWithLifecycle(initialValue = false)
val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
val interactionSource = remember { MutableInteractionSource() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index 8129e41..ba25719 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -21,11 +21,11 @@
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.plugins.clocks.ClockController
@@ -37,7 +37,7 @@
fun rememberBurnIn(
clockInteractor: KeyguardClockInteractor,
): BurnInState {
- val clock by clockInteractor.currentClock.collectAsState()
+ val clock by clockInteractor.currentClock.collectAsStateWithLifecycle()
val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) }
val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 3152535..abff93d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -22,13 +22,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
@@ -67,8 +67,8 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
- viewModel.shouldUseSplitNotificationShade.collectAsState()
- val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
+ viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
viewModel = viewModel.longPress,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 9d31955..c83f62c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -22,13 +22,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
@@ -70,8 +70,8 @@
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
- viewModel.shouldUseSplitNotificationShade.collectAsState()
- val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
+ viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
viewModel = viewModel.longPress,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index abbf0ea..aaf49ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.composable.modifier
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -25,6 +24,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
@@ -44,8 +44,10 @@
val translationYState = remember { mutableStateOf(0F) }
val copiedParams = params.copy(translationY = { translationYState.value })
val burnIn = viewModel.movement(copiedParams)
- val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
- val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+ val translationX by
+ burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
+ val translationY by
+ burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
translationYState.value = translationY
val scaleViewModel by
burnIn
@@ -55,7 +57,7 @@
scaleClockOnly = it.scaleClockOnly,
)
}
- .collectAsState(initial = BurnInScaleViewModel())
+ .collectAsStateWithLifecycle(initialValue = BurnInScaleViewModel())
return this.graphicsLayer {
this.translationX = if (isClock) 0F else translationX
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 09ec76d..218779d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -25,13 +25,13 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.contains
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.customization.R
@@ -59,9 +59,11 @@
onTopChanged: (top: Float?) -> Unit,
modifier: Modifier = Modifier,
) {
- val currentClock by viewModel.currentClock.collectAsState()
+ val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
val smallTopMargin by
- viewModel.smallClockTopMargin.collectAsState(viewModel.getSmallClockTopMargin())
+ viewModel.smallClockTopMargin.collectAsStateWithLifecycle(
+ viewModel.getSmallClockTopMargin()
+ )
if (currentClock?.smallClock?.view == null) {
return
}
@@ -89,7 +91,7 @@
@Composable
fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) {
- val currentClock by viewModel.currentClock.collectAsState()
+ val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
if (currentClock?.largeClock?.view == null) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index c37d626..3ca2b9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -18,9 +18,9 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
@@ -40,7 +40,7 @@
@Composable
fun SceneScope.KeyguardMediaCarousel() {
- val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsState()
+ val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle()
MediaCarousel(
isVisible = isMediaVisible,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f48fa88..7f80dfa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -20,13 +20,13 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
@@ -40,16 +40,19 @@
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import dagger.Lazy
import javax.inject.Inject
@SysUISingleton
class NotificationSection
@Inject
constructor(
+ private val stackScrollView: Lazy<NotificationScrollView>,
private val viewModel: NotificationsPlaceholderViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
sharedNotificationContainer: SharedNotificationContainer,
@@ -88,9 +91,9 @@
@Composable
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
val shouldUseSplitNotificationShade by
- lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
+ lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
val areNotificationsVisible by
- lockscreenContentViewModel.areNotificationsVisible.collectAsState()
+ lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
if (Flags.centralizedStatusBarHeightFix()) {
LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -103,6 +106,7 @@
}
ConstrainedNotificationStack(
+ stackScrollView = stackScrollView.get(),
viewModel = viewModel,
modifier =
modifier
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index fc8b3b9..44bda95 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -26,7 +26,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -34,6 +33,7 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
@@ -160,7 +160,7 @@
private fun Weather(
modifier: Modifier = Modifier,
) {
- val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsState()
+ val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsStateWithLifecycle()
if (!isVisible) {
return
}
@@ -187,7 +187,7 @@
private fun Date(
modifier: Modifier = Modifier,
) {
- val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsState()
+ val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsStateWithLifecycle()
if (!isVisible) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 63c70c9..88b8298 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -25,7 +25,6 @@
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -33,6 +32,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.thenIf
@@ -62,9 +62,9 @@
fun DefaultClockLayout(
modifier: Modifier = Modifier,
) {
- val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
+ val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
val hasCustomPositionUpdatedAnimation by
- clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState()
+ clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle()
val currentScene =
when (currentClockLayout) {
KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
@@ -133,7 +133,7 @@
@Composable
private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
val burnIn = rememberBurnIn(clockInteractor)
- val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+ val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
LaunchedEffect(isLargeClockVisible) {
if (isLargeClockVisible) {
@@ -170,8 +170,8 @@
@Composable
private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
val burnIn = rememberBurnIn(clockInteractor)
- val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
- val currentClockState = clockViewModel.currentClock.collectAsState()
+ val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
+ val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle()
LaunchedEffect(isLargeClockVisible) {
if (isLargeClockVisible) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 01d62a3..cf2e895 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -38,7 +38,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
@@ -64,6 +63,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
@@ -112,7 +112,7 @@
modifier: Modifier = Modifier,
isPeekFromBottom: Boolean = false,
) {
- val headsUpHeight = viewModel.headsUpHeight.collectAsState()
+ val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle()
Element(
Notifications.Elements.HeadsUpNotificationPlaceholder,
@@ -138,6 +138,7 @@
/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.ConstrainedNotificationStack(
+ stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
@@ -146,6 +147,7 @@
modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) }
) {
NotificationPlaceholder(
+ stackScrollView = stackScrollView,
viewModel = viewModel,
modifier = Modifier.fillMaxSize(),
)
@@ -178,9 +180,10 @@
shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
ScrollState(initial = 0)
}
- val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
- val isCurrentGestureOverscroll = viewModel.isCurrentGestureOverscroll.collectAsState(false)
- val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+ val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f)
+ val isCurrentGestureOverscroll =
+ viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
+ val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
val navBarHeight =
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
@@ -193,7 +196,8 @@
*/
val stackHeight = remember { mutableIntStateOf(0) }
- val scrimRounding = viewModel.shadeScrimRounding.collectAsState(ShadeScrimRounding())
+ val scrimRounding =
+ viewModel.shadeScrimRounding.collectAsStateWithLifecycle(ShadeScrimRounding())
// the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
// calculated in minScrimOffset. The scrim is the same height as the screen minus the
@@ -334,6 +338,7 @@
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
NotificationPlaceholder(
+ stackScrollView = stackScrollView,
viewModel = viewModel,
modifier =
Modifier.verticalNestedScrollToScene(
@@ -390,6 +395,7 @@
@Composable
private fun SceneScope.NotificationPlaceholder(
+ stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
@@ -408,10 +414,8 @@
" bounds=${coordinates.boundsInWindow()}"
}
// NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not
- viewModel.onStackBoundsChanged(
- top = positionInWindow.y,
- bottom = positionInWindow.y + coordinates.size.height,
- )
+ stackScrollView.setStackTop(positionInWindow.y)
+ stackScrollView.setStackBottom(positionInWindow.y + coordinates.size.height)
}
) {
content {}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 73cb72c..b808044 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -36,7 +36,6 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
@@ -46,6 +45,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
@@ -64,8 +64,8 @@
viewModel: PeopleViewModel,
onResult: (PeopleViewModel.Result) -> Unit,
) {
- val priorityTiles by viewModel.priorityTiles.collectAsState()
- val recentTiles by viewModel.recentTiles.collectAsState()
+ val priorityTiles by viewModel.priorityTiles.collectAsStateWithLifecycle()
+ val recentTiles by viewModel.recentTiles.collectAsStateWithLifecycle()
// Call [onResult] this activity when the ViewModel tells us so.
LaunchedEffect(viewModel.result) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 2f241ce..e8da4bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -44,7 +44,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -69,6 +68,7 @@
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.SceneScope
@@ -132,8 +132,8 @@
val context = LocalContext.current
// Collect alphas as soon as we are composed, even when not visible.
- val alpha by viewModel.alpha.collectAsState()
- val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
+ val alpha by viewModel.alpha.collectAsStateWithLifecycle()
+ val backgroundAlpha = viewModel.backgroundAlpha.collectAsStateWithLifecycle()
var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) }
var foregroundServices by remember {
@@ -181,7 +181,6 @@
val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding)
Row(
modifier
- .sysuiResTag("qs_footer_actions")
.fillMaxWidth()
.graphicsLayer { this.alpha = alpha }
.then(backgroundModifier)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
index ca6b343..73a624a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -21,13 +21,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.modifiers.height
import com.android.compose.modifiers.width
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -40,13 +40,13 @@
qsSceneAdapter: QSSceneAdapter,
modifier: Modifier = Modifier,
) {
- val isShowing by viewModel.isShowing.collectAsState()
+ val isShowing by viewModel.isShowing.collectAsStateWithLifecycle()
val mirrorAlpha by
animateFloatAsState(
targetValue = if (isShowing) 1f else 0f,
label = "alphaAnimationBrightnessMirrorShowing",
)
- val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+ val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle()
val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 46be6b8..d109988 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -22,18 +22,19 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.MovableElementScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.modifiers.thenIf
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
@@ -143,7 +144,9 @@
MovableElement(
key = QuickSettings.Elements.Content,
modifier =
- modifier.fillMaxWidth().layout { measurable, constraints ->
+ modifier.sysuiResTag("quick_settings_panel").fillMaxWidth().layout {
+ measurable,
+ constraints ->
val placeable = measurable.measure(constraints)
// Use the height of the correct view based on the scene it is being composed in
val height = heightProvider().coerceAtLeast(0)
@@ -161,9 +164,11 @@
state: QSSceneAdapter.State,
modifier: Modifier = Modifier,
) {
- val qsView by qsSceneAdapter.qsView.collectAsState(null)
+ val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null)
val isCustomizing by
- qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value)
+ qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle(
+ qsSceneAdapter.isCustomizerShowing.value
+ )
QuickSettingsTheme {
val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6ae0efa..d76b19f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -51,7 +51,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -63,6 +62,7 @@
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.animateSceneFloatAsState
@@ -163,7 +163,8 @@
) {
val cutoutLocation = LocalDisplayCutout.current.location
- val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val brightnessMirrorShowing by
+ viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -198,10 +199,11 @@
Modifier.displayCutoutPadding()
},
) {
- val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
- val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+ val isCustomizerShowing by
+ viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
- viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
val screenHeight = LocalRawScreenHeight.current
BackHandler(
@@ -343,10 +345,10 @@
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
isSplitShade = false,
- modifier = Modifier.sysuiResTag("quick_settings_panel")
+ modifier = Modifier
)
- val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+ val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
MediaCarousel(
isVisible = isMediaVisible,
@@ -362,7 +364,8 @@
isCustomizing = isCustomizing,
customizingAnimationDuration = customizingAnimationDuration,
lifecycleOwner = lifecycleOwner,
- modifier = Modifier.align(Alignment.CenterHorizontally),
+ modifier =
+ Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"),
)
}
NotificationScrollingStack(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 7af9b7b..92b2b4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -23,7 +23,6 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -34,6 +33,7 @@
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -68,8 +68,9 @@
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState()
- val currentDestinations by viewModel.currentDestinationScenes(coroutineScope).collectAsState()
+ val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
+ val currentDestinations by
+ viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle()
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index d528736..00ef11d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -30,13 +30,13 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
@@ -51,7 +51,7 @@
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
- val backgroundScene by viewModel.backgroundScene.collectAsState()
+ val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
Box(modifier) {
if (backgroundScene == Scenes.Lockscreen) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 709a416..ac3e015 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -35,7 +35,6 @@
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -52,6 +51,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
@@ -118,7 +118,7 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val isDisabled by viewModel.isDisabled.collectAsState()
+ val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
}
@@ -138,7 +138,7 @@
}
}
- val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
+ val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
// This layout assumes it is globally positioned at (0, 0) and is the
// same size as the screen.
@@ -271,7 +271,7 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val isDisabled by viewModel.isDisabled.collectAsState()
+ val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
}
@@ -280,7 +280,7 @@
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
}
- val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
+ val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
if (isPrivacyChipVisible) {
@@ -435,7 +435,7 @@
modifier: Modifier = Modifier,
) {
Row(modifier = modifier) {
- val subIds by viewModel.mobileSubIds.collectAsState()
+ val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
for (subId in subIds) {
Spacer(modifier = Modifier.width(5.dp))
@@ -472,10 +472,12 @@
val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone)
val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location)
- val isSingleCarrier by viewModel.isSingleCarrier.collectAsState()
- val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState()
- val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState()
- val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState()
+ val isSingleCarrier by viewModel.isSingleCarrier.collectAsStateWithLifecycle()
+ val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsStateWithLifecycle()
+ val isMicCameraIndicationEnabled by
+ viewModel.isMicCameraIndicationEnabled.collectAsStateWithLifecycle()
+ val isLocationIndicationEnabled by
+ viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle()
AndroidView(
factory = { context ->
@@ -544,7 +546,7 @@
viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val privacyList by viewModel.privacyItems.collectAsState()
+ val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle()
AndroidView(
factory = { context ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 42e6fcc..a0278a6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -45,7 +45,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -58,6 +57,7 @@
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
@@ -71,6 +71,7 @@
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -177,7 +178,7 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
- val shadeMode by viewModel.shadeMode.collectAsState()
+ val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
when (shadeMode) {
is ShadeMode.Single ->
SingleShade(
@@ -228,8 +229,8 @@
key = QuickSettings.SharedValues.TilesSquishiness,
canOverflow = false
)
- val isClickable by viewModel.isClickable.collectAsState()
- val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+ val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
+ val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
val shouldPunchHoleBehindScrim =
layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -335,10 +336,11 @@
) {
val screenCornerRadius = LocalScreenCornerRadius.current
- val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
- val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+ val isCustomizerShowing by
+ viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
- viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
+ viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
@@ -353,13 +355,13 @@
.unfoldTranslationX(
isOnStartSide = true,
)
- .collectAsState(0f)
+ .collectAsStateWithLifecycle(0f)
val unfoldTranslationXForEndSide by
viewModel
.unfoldTranslationX(
isOnStartSide = false,
)
- .collectAsState(0f)
+ .collectAsStateWithLifecycle(0f)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val bottomPadding by
@@ -383,7 +385,8 @@
}
}
- val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val brightnessMirrorShowing by
+ viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -393,7 +396,7 @@
viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
- val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+ val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
@@ -444,6 +447,7 @@
Column(
modifier =
Modifier.fillMaxSize()
+ .sysuiResTag("expanded_qs_scroll_view")
.weight(1f)
.thenIf(!isCustomizerShowing) {
Modifier.verticalNestedScrollToScene()
@@ -482,6 +486,7 @@
lifecycleOwner = lifecycleOwner,
modifier =
Modifier.align(Alignment.CenterHorizontally)
+ .sysuiResTag("qs_footer_actions")
.then(brightnessMirrorShowingModifier),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 5e107c6..931ff56 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -3,9 +3,9 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
@@ -14,8 +14,8 @@
viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val longerText = viewModel.longerDateText.collectAsState()
- val shorterText = viewModel.shorterDateText.collectAsState()
+ val longerText = viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle()
Layout(
contents =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
index 79d17ef..3976c61 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -16,29 +16,40 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.liveRegion
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import javax.inject.Inject
@@ -52,34 +63,47 @@
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
- val slice by viewModel.buttonSlice.collectAsState()
+ val slice by viewModel.buttonSlice.collectAsStateWithLifecycle()
val label = stringResource(R.string.volume_panel_noise_control_title)
+ val screenWidth: Float =
+ with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+ var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
val isClickable = viewModel.isClickable(slice)
- val onClick =
- if (isClickable) {
- { ancPopup.show(null) }
- } else {
- null
- }
Column(
- modifier = modifier,
+ modifier =
+ modifier.onGloballyPositioned {
+ gravity = VolumePanelPopup.calculateGravity(it, screenWidth)
+ },
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- SliceAndroidView(
- modifier =
- Modifier.height(64.dp)
- .fillMaxWidth()
- .semantics {
- role = Role.Button
+ Box(
+ modifier = Modifier.height(64.dp),
+ ) {
+ SliceAndroidView(
+ modifier = modifier.fillMaxSize(),
+ slice = slice,
+ onWidthChanged = viewModel::onButtonSliceWidthChanged,
+ enableAccessibility = false,
+ )
+ Button(
+ modifier =
+ modifier.fillMaxSize().padding(8.dp).semantics {
+ liveRegion = LiveRegionMode.Polite
contentDescription = label
- }
- .clip(RoundedCornerShape(28.dp)),
- slice = slice,
- isEnabled = onClick != null,
- onWidthChanged = viewModel::onButtonSliceWidthChanged,
- onClick = onClick,
- )
+ },
+ enabled = isClickable,
+ onClick = { with(ancPopup) { show(null, gravity) } },
+ colors =
+ ButtonColors(
+ contentColor = Color.Transparent,
+ containerColor = Color.Transparent,
+ disabledContentColor = Color.Transparent,
+ disabledContainerColor = Color.Transparent,
+ )
+ ) {}
+ }
+
Text(
modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index e1ee01e..15df1be 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -16,17 +16,18 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.slice.Slice
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
@@ -47,9 +48,10 @@
) {
/** Shows a popup with the [expandable] animation. */
- fun show(expandable: Expandable?) {
+ fun show(expandable: Expandable?, horizontalGravity: Int) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
- volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ val gravity = horizontalGravity or Gravity.BOTTOM
+ volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
}
@Composable
@@ -65,14 +67,14 @@
@Composable
private fun Content(dialog: SystemUIDialog) {
- val isAvailable by viewModel.isAvailable.collectAsState(true)
+ val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true)
if (!isAvailable) {
SideEffect { dialog.dismiss() }
return
}
- val slice by viewModel.popupSlice.collectAsState()
+ val slice by viewModel.popupSlice.collectAsStateWithLifecycle()
SliceAndroidView(
modifier = Modifier.fillMaxWidth(),
slice = slice,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index fc5d212..23d50c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -16,11 +16,12 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
-import android.annotation.SuppressLint
import android.content.Context
+import android.os.Bundle
import android.view.ContextThemeWrapper
-import android.view.MotionEvent
import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
@@ -32,14 +33,13 @@
fun SliceAndroidView(
slice: Slice?,
modifier: Modifier = Modifier,
- isEnabled: Boolean = true,
onWidthChanged: ((Int) -> Unit)? = null,
- onClick: (() -> Unit)? = null,
+ enableAccessibility: Boolean = true,
) {
AndroidView(
modifier = modifier,
factory = { context: Context ->
- ClickableSliceView(
+ ComposeSliceView(
ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
)
.apply {
@@ -47,17 +47,18 @@
isScrollable = false
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
setShowTitleItems(true)
- if (onWidthChanged != null) {
- addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
- }
}
},
- update = { sliceView: ClickableSliceView ->
+ update = { sliceView: ComposeSliceView ->
sliceView.slice = slice
- sliceView.onClick = onClick
- sliceView.isEnabled = isEnabled
- sliceView.isClickable = isEnabled
- }
+ sliceView.layoutListener = onWidthChanged?.let(::OnWidthChangedLayoutListener)
+ sliceView.enableAccessibility = enableAccessibility
+ },
+ onRelease = { sliceView: ComposeSliceView ->
+ sliceView.layoutListener = null
+ sliceView.slice = null
+ sliceView.enableAccessibility = true
+ },
)
}
@@ -83,26 +84,39 @@
}
}
-/**
- * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice
- * first.
- */
-@SuppressLint("ViewConstructor") // only used in this class
-private class ClickableSliceView(context: Context) : SliceView(context) {
+private class ComposeSliceView(context: Context) : SliceView(context) {
- var onClick: (() -> Unit)? = null
+ var enableAccessibility: Boolean = true
+ var layoutListener: OnLayoutChangeListener? = null
+ set(value) {
+ field?.let { removeOnLayoutChangeListener(it) }
+ field = value
+ field?.let { addOnLayoutChangeListener(it) }
+ }
- init {
- if (onClick != null) {
- setOnClickListener {}
+ override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
+ if (enableAccessibility) {
+ super.onInitializeAccessibilityNodeInfo(info)
}
}
- override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
- return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
+ override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
+ if (enableAccessibility) {
+ super.onInitializeAccessibilityEvent(event)
+ }
}
- override fun onClick(v: View?) {
- onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
+ override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
+ return if (enableAccessibility) {
+ super.performAccessibilityAction(action, arguments)
+ } else {
+ false
+ }
+ }
+
+ override fun addChildrenForAccessibility(outChildren: ArrayList<View>?) {
+ if (enableAccessibility) {
+ super.addChildrenForAccessibility(outChildren)
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 0893b9d..e1ae80f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.button.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -27,20 +28,27 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup.Companion.calculateGravity
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import kotlinx.coroutines.flow.StateFlow
@@ -48,17 +56,22 @@
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onClick: (Expandable) -> Unit
+ private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
) : ComposeVolumePanelUiComponent {
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
- val viewModelByState by viewModelFlow.collectAsState()
+ val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
val viewModel = viewModelByState ?: return
val label = viewModel.label.toString()
+ val screenWidth: Float =
+ with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+ var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
+
Column(
- modifier = modifier,
+ modifier =
+ modifier.onGloballyPositioned { gravity = calculateGravity(it, screenWidth) },
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -82,7 +95,7 @@
} else {
MaterialTheme.colorScheme.onSurface
},
- onClick = onClick,
+ onClick = { onClick(it, gravity) },
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 12debbc..1b821d3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -29,7 +29,6 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -42,6 +41,7 @@
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -56,7 +56,7 @@
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
- val viewModelByState by viewModelFlow.collectAsState()
+ val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
val viewModel = viewModelByState ?: return
val label = viewModel.label.toString()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index ded63a1..237bbfd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -45,7 +45,6 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -55,6 +54,7 @@
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
@@ -77,9 +77,9 @@
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val connectedDeviceViewModel: ConnectedDeviceViewModel? by
- viewModel.connectedDeviceViewModel.collectAsState()
+ viewModel.connectedDeviceViewModel.collectAsStateWithLifecycle()
val deviceIconViewModel: DeviceIconViewModel? by
- viewModel.deviceIconViewModel.collectAsState()
+ viewModel.deviceIconViewModel.collectAsStateWithLifecycle()
val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
Expandable(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index bb4e957..3b1bf2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.popup.ui.composable
import android.view.Gravity
+import androidx.annotation.GravityInt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -31,6 +32,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.paneTitle
@@ -60,13 +63,14 @@
*/
fun show(
expandable: Expandable?,
+ @GravityInt gravity: Int,
title: @Composable (SystemUIDialog) -> Unit,
content: @Composable (SystemUIDialog) -> Unit,
) {
val dialog =
dialogFactory.create(
theme = R.style.Theme_VolumePanel_Popup,
- dialogGravity = Gravity.BOTTOM,
+ dialogGravity = gravity,
) {
PopupComposable(it, title, content)
}
@@ -122,4 +126,23 @@
}
}
}
+
+ companion object {
+
+ /**
+ * Returns absolute ([Gravity.LEFT], [Gravity.RIGHT] or [Gravity.CENTER_HORIZONTAL])
+ * [GravityInt] for the popup based on the [coordinates] global position relative to the
+ * [screenWidthPx].
+ */
+ @GravityInt
+ fun calculateGravity(coordinates: LayoutCoordinates, screenWidthPx: Float): Int {
+ val bottomCenter: Float = coordinates.boundsInRoot().bottomCenter.x
+ val rootBottomCenter: Float = screenWidthPx / 2
+ return when {
+ bottomCenter < rootBottomCenter -> Gravity.LEFT
+ bottomCenter > rootBottomCenter -> Gravity.RIGHT
+ else -> Gravity.CENTER_HORIZONTAL
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 12d2bc2..9891b5b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -16,17 +16,18 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
@@ -47,14 +48,15 @@
) {
/** Shows a popup with the [expandable] animation. */
- fun show(expandable: Expandable) {
+ fun show(expandable: Expandable, horizontalGravity: Int) {
uiEventLogger.logWithPosition(
VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
0,
null,
viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
)
- volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ val gravity = horizontalGravity or Gravity.BOTTOM
+ volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
}
@Composable
@@ -70,14 +72,14 @@
@Composable
private fun Content(dialog: SystemUIDialog) {
- val isAvailable by viewModel.isAvailable.collectAsState()
+ val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle()
if (!isAvailable) {
SideEffect { dialog.dismiss() }
return
}
- val enabledModelStates by viewModel.spatialAudioButtons.collectAsState()
+ val enabledModelStates by viewModel.spatialAudioButtons.collectAsStateWithLifecycle()
if (enabledModelStates.isEmpty()) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 1def7fe..072e91a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -40,7 +40,6 @@
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -52,6 +51,7 @@
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
import com.android.systemui.res.R
@@ -84,7 +84,7 @@
modifier = Modifier.fillMaxWidth(),
) {
val sliderViewModel: SliderViewModel = viewModels.first()
- val sliderState by viewModels.first().slider.collectAsState()
+ val sliderState by viewModels.first().slider.collectAsStateWithLifecycle()
val sliderPadding by topSliderPadding(isExpandable)
VolumeSlider(
@@ -119,7 +119,7 @@
Column {
for (index in 1..viewModels.lastIndex) {
val sliderViewModel: SliderViewModel = viewModels[index]
- val sliderState by sliderViewModel.slider.collectAsState()
+ val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle()
transition.AnimatedVisibility(
modifier = Modifier.padding(top = 16.dp),
visible = { it || !isExpandable },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index bb17499..d15430f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -18,9 +18,9 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformSliderColors
import com.android.compose.grid.VerticalGrid
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -39,7 +39,7 @@
horizontalSpacing = 24.dp,
) {
for (sliderViewModel in viewModels) {
- val sliderState = sliderViewModel.slider.collectAsState().value
+ val sliderState = sliderViewModel.slider.collectAsStateWithLifecycle().value
VolumeSlider(
modifier = Modifier.fillMaxWidth(),
state = sliderState,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index cb3867f..271eb96 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -78,12 +78,7 @@
}
state.a11yStateDescription?.let { stateDescription = it }
- ?: run {
- // provide a not animated value to the a11y because it fails to announce
- // the settled value when it changes rapidly.
- progressBarRangeInfo =
- ProgressBarRangeInfo(state.value, state.valueRange)
- }
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
} else {
disabled()
contentDescription =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 79056b2..770c5d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -18,9 +18,9 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformSliderDefaults
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel
@@ -38,7 +38,8 @@
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
- val sliderViewModels: List<SliderViewModel> by viewModel.sliderViewModels.collectAsState()
+ val sliderViewModels: List<SliderViewModel> by
+ viewModel.sliderViewModels.collectAsStateWithLifecycle()
if (sliderViewModels.isEmpty()) {
return
}
@@ -52,7 +53,7 @@
val expandableViewModel: SlidersExpandableViewModel by
viewModel
.isExpandable(isPortrait)
- .collectAsState(SlidersExpandableViewModel.Unavailable)
+ .collectAsStateWithLifecycle(SlidersExpandableViewModel.Unavailable)
if (expandableViewModel is SlidersExpandableViewModel.Unavailable) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index a602e25..83b8158 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -24,7 +24,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -32,6 +31,7 @@
import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
@@ -54,8 +54,8 @@
}
val accessibilityTitle = stringResource(R.string.accessibility_volume_settings)
- val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
- val components by viewModel.componentsLayout.collectAsState(null)
+ val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle()
+ val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null)
with(VolumePanelComposeScope(state)) {
components?.let { componentsState ->
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
index bac3553..95a25c5 100755
--- a/packages/SystemUI/flag_check.py
+++ b/packages/SystemUI/flag_check.py
@@ -12,19 +12,20 @@
%s
The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
-
-As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
-flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag.
+For legacy flags use EXEMPT with your flag name.
Some examples below:
-Flag: NONE
-Flag: NA
-Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
-Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
-Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+Flag: NONE Repohook Update
+Flag: TEST_ONLY
+Flag: EXEMPT resource only update
+Flag: EXEMPT bugfix
+Flag: EXEMPT refactor
+Flag: com.android.launcher3.enable_twoline_allapps
+Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader
-Check the git history for more examples. It's a regex matched field.
+Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats.
"""
def main():
@@ -63,28 +64,31 @@
return
field = 'Flag'
- none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+ none = 'NONE'
+ testOnly = 'TEST_ONLY'
+ docsOnly = 'DOCS_ONLY'
+ exempt = 'EXEMPT'
+ justification = '<justification>'
- typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
-
- # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
- # Aconfig Flag name format = "packageName"."flagName"
+ # Aconfig Flag name format = <packageName>.<flagName>
# package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
- # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+ # For now alphabets, digits, "_", "." characters are allowed in flag name.
+ # Checks if there is "one dot" between packageName and flagName and not adding stricter format check
#common_typos_disable
- flagName = '([a-zA-z0-9_.])+'
+ flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)'
- #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
- stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+ # None and Exempt needs justification
+ exemptRegex = fr'{exempt}\s*[a-zA-Z]+'
+ noneRegex = fr'{none}\s*[a-zA-Z]+'
#common_typos_enable
- readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD'
+ readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly
flagRegex = fr'^{field}: .*$'
check_flag = re.compile(flagRegex) #Flag:
# Ignore case for flag name format.
- flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+ flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*'
check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
flagError = False
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 47a00f4..624f18d 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -133,7 +133,7 @@
@ColorInt seed: Int,
darkTheme: Boolean,
style: Style
- ) : this(seed, darkTheme, style, 0.5)
+ ) : this(seed, darkTheme, style, 0.0)
@JvmOverloads
constructor(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 11a4241..27bffd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -18,10 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -30,6 +28,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -37,7 +36,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -51,89 +49,66 @@
CentralSurfaces mCentralSurfaces;
@Mock
+ ShadeViewController mShadeViewController;
+
+ @Mock
TouchHandler.TouchSession mTouchSession;
ShadeTouchHandler mTouchHandler;
- @Captor
- ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor;
- @Captor
- ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor;
-
private static final int TOUCH_HEIGHT = 20;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
-
- mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), TOUCH_HEIGHT);
- }
-
- // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
- @Test
- public void testSwipeDown_captured() {
- final boolean captured = swipe(Direction.DOWN);
-
- assertThat(captured).isTrue();
- }
-
- // Verifies that a swipe in the upward direction is not catpured.
- @Test
- public void testSwipeUp_notCaptured() {
- final boolean captured = swipe(Direction.UP);
-
- // Motion events not captured as the swipe is going in the wrong direction.
- assertThat(captured).isFalse();
- }
-
- // Verifies that a swipe down forwards captured touches to the shade window for handling.
- @Test
- public void testSwipeDown_sentToShadeWindow() {
- swipe(Direction.DOWN);
-
- // Both motion events are sent for the shade window to process.
- verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
- }
-
- // Verifies that a swipe down is not forwarded to the shade window.
- @Test
- public void testSwipeUp_touchesNotSent() {
- swipe(Direction.UP);
-
- // Motion events are not sent for the shade window to process as the swipe is going in the
- // wrong direction.
- verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
+ mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
+ TOUCH_HEIGHT);
}
/**
- * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
- * touch handler's gesture listener.
- * <p>
- * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
- * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0.
+ * Verify that touches aren't handled when the bouncer is showing.
*/
- private boolean swipe(Direction direction) {
- Mockito.clearInvocations(mTouchSession);
+ @Test
+ public void testInactiveOnBouncer() {
+ when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
mTouchHandler.onSessionStart(mTouchSession);
-
- verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture());
- verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture());
-
- final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0;
- final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT;
-
- // Send touches to the input and gesture listener.
- final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0);
- final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0);
- mInputListenerCaptor.getValue().onInputEvent(event1);
- mInputListenerCaptor.getValue().onInputEvent(event2);
- final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0,
- startY - endY);
-
- return captured;
+ verify(mTouchSession).pop();
}
- private enum Direction {
- DOWN, UP,
+ /**
+ * Make sure {@link ShadeTouchHandler}
+ */
+ @Test
+ public void testTouchPilferingOnScroll() {
+ final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+ final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+ assertThat(gestureListenerArgumentCaptor.getValue()
+ .onScroll(motionEvent1, motionEvent2, 1, 1))
+ .isTrue();
}
+
+ /**
+ * Ensure touches are propagated to the {@link ShadeViewController}.
+ */
+ @Test
+ public void testEventPropagation() {
+ final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<InputChannelCompat.InputEventListener>
+ inputEventListenerArgumentCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+ inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+ verify(mShadeViewController).handleExternalTouch(motionEvent);
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index ecfcc90..a5acf72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -66,15 +66,12 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private val sceneContainerStartable = kosmos.sceneContainerStartable
private lateinit var underTest: BouncerViewModel
@Before
fun setUp() {
- sceneContainerStartable.start()
+ kosmos.sceneContainerStartable.start()
underTest = kosmos.bouncerViewModel
}
@@ -164,11 +161,11 @@
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- bouncerInteractor.authenticate(WRONG_PIN)
+ kosmos.bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(isInputEnabled).isFalse()
- val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0
advanceTimeBy(lockoutEndMs - testScope.currentTime)
assertThat(isInputEnabled).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
index 312c14d..fec56ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -18,9 +18,13 @@
import android.content.applicationContext
import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
import androidx.test.filters.SmallTest
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
@@ -40,15 +44,19 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
private val kosmos = testKosmos()
- private val fakeUserRepository = kosmos.fakeUserRepository
-
private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
@@ -130,7 +138,83 @@
}
}
- private companion object {
- val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ @Test
+ @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ fun brightnessBaseUserRestriction_flagOff_noRestriction() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+ }
+ }
+
+ @Test
+ fun bothRestrictions_returnsSetEnforcedAdminFromCheck() =
+ with(kosmos) {
+ testScope.runTest {
+ val enforcedAdmin: EnforcedAdmin =
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+ whenever(
+ mockUserRestrictionChecker.checkIfRestrictionEnforced(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(enforcedAdmin)
+
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ fun brightnessBaseUserRestriction_flagOn_emptyRestriction() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
+ }
+ }
+
+ companion object {
+ private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
index 85a4bcf..11f5238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -48,7 +48,6 @@
private val kosmos = testKosmos()
private val mockActivityStarter = kosmos.activityStarter
- private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
private val underTest =
with(kosmos) {
@@ -70,7 +69,18 @@
fakeBrightnessPolicyRepository.setCurrentUserRestricted()
- assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+ assertThat(restriction)
+ .isEqualTo(
+ PolicyRestriction.Restricted(
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+ BrightnessPolicyRepository.RESTRICTION
+ )
+ )
+ )
+
+ fakeBrightnessPolicyRepository.setBaseUserRestriction()
+
+ assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index b4b812d..0ab0959 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -265,7 +265,7 @@
with(kosmos) {
testScope.runTest {
// Device is dreaming and on communal.
- fakeKeyguardRepository.setDreaming(true)
+ updateDreaming(true)
communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
@@ -282,7 +282,7 @@
with(kosmos) {
testScope.runTest {
// Device is not dreaming and on communal.
- fakeKeyguardRepository.setDreaming(false)
+ updateDreaming(false)
communalInteractor.changeScene(CommunalScenes.Communal)
// Scene stays as Communal
@@ -297,7 +297,7 @@
with(kosmos) {
testScope.runTest {
// Device is dreaming and on communal.
- fakeKeyguardRepository.setDreaming(true)
+ updateDreaming(true)
communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
@@ -309,7 +309,7 @@
// Dream stops, timeout is cancelled and device stays on hub, because the regular
// screen timeout will take effect at this point.
- fakeKeyguardRepository.setDreaming(false)
+ updateDreaming(false)
advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
}
@@ -320,7 +320,7 @@
with(kosmos) {
testScope.runTest {
// Device is on communal, but not dreaming.
- fakeKeyguardRepository.setDreaming(false)
+ updateDreaming(false)
communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
@@ -328,7 +328,7 @@
// Wait a bit, but not long enough to timeout, then start dreaming.
advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
- fakeKeyguardRepository.setDreaming(true)
+ updateDreaming(true)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
// Device times out after one screen timeout interval, dream doesn't reset timeout.
@@ -338,11 +338,31 @@
}
@Test
+ fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is on communal.
+ communalInteractor.changeScene(CommunalScenes.Communal)
+
+ // Device stays on the hub after the timeout since we're not dreaming.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Start dreaming.
+ updateDreaming(true)
+
+ // Hub times out immediately.
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
fun hubTimeout_userActivityTriggered_resetsTimeout() =
with(kosmos) {
testScope.runTest {
// Device is dreaming and on communal.
- fakeKeyguardRepository.setDreaming(true)
+ updateDreaming(true)
communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
@@ -371,7 +391,7 @@
fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
// Device is dreaming and on communal.
- fakeKeyguardRepository.setDreaming(true)
+ updateDreaming(true)
communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
@@ -395,6 +415,12 @@
runCurrent()
}
+ private fun TestScope.updateDreaming(dreaming: Boolean) =
+ with(kosmos) {
+ fakeKeyguardRepository.setDreaming(dreaming)
+ runCurrent()
+ }
+
private suspend fun TestScope.enableCommunal() =
with(kosmos) {
setCommunalAvailable(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1cdc2b6..407bf4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -114,7 +114,7 @@
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataRemoved("key")
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
runCurrent()
// Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index ce7b60e..325a324 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
@@ -202,6 +203,18 @@
.isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
}
+ @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT)
+ @Test
+ fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() =
+ testScope.runTest {
+ val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER))
+ assertThat(setting?.categories)
+ .isEqualTo(
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD +
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+ )
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
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 2d079d7..be44339 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
@@ -51,6 +51,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -141,6 +142,7 @@
testScope,
context.resources,
kosmos.keyguardTransitionInteractor,
+ kosmos.keyguardInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index e3dd9ae..f5c86e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -27,11 +27,15 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.DreamManager;
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper.RunWithLooper;
import android.view.AttachedSurfaceControl;
+import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -41,12 +45,15 @@
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.BlurUtils;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -91,6 +98,9 @@
ViewGroup mDreamOverlayContentView;
@Mock
+ View mHubGestureIndicatorView;
+
+ @Mock
Handler mHandler;
@Mock
@@ -115,6 +125,12 @@
DreamOverlayStateController mStateController;
@Mock
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock
+ ShadeInteractor mShadeInteractor;
+ @Mock
+ CommunalInteractor mCommunalInteractor;
+ @Mock
+ private DreamManager mDreamManager;
DreamOverlayContainerViewController mController;
@@ -133,6 +149,7 @@
mDreamOverlayContainerView,
mComplicationHostViewController,
mDreamOverlayContentView,
+ mHubGestureIndicatorView,
mDreamOverlayStatusBarViewController,
mLowLightTransitionCoordinator,
mBlurUtils,
@@ -146,7 +163,22 @@
mAnimationsController,
mStateController,
mBouncerlessScrimController,
- mKeyguardTransitionInteractor);
+ mKeyguardTransitionInteractor,
+ mShadeInteractor,
+ mCommunalInteractor,
+ mDreamManager);
+ }
+
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ public void testHubGestureIndicatorGoneWhenFlagOff() {
+ verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE);
+ }
+
+ @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE})
+ @Test
+ public void testHubGestureIndicatorVisibleWhenFlagOn() {
+ verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index e3c6dee..29fbee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -108,7 +108,7 @@
mTouchHandler.onSessionStart(mTouchSession);
verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
- verify(mCentralSurfaces).handleExternalShadeWindowTouch(motionEvent);
+ verify(mCentralSurfaces).handleDreamTouch(motionEvent);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
new file mode 100644
index 0000000..1e7ed63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FingerprintPropertyRepositoryTest : SysuiTestCase() {
+ @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+ private lateinit var underTest: FingerprintPropertyRepositoryImpl
+ @Mock private lateinit var fingerprintManager: FingerprintManager
+ @Captor
+ private lateinit var fingerprintCallbackCaptor:
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
+
+ @Before
+ fun setup() {
+ underTest =
+ FingerprintPropertyRepositoryImpl(
+ testScope.backgroundScope,
+ Dispatchers.Main.immediate,
+ fingerprintManager,
+ )
+ }
+
+ @Test
+ fun propertiesInitialized_onStartFalse() =
+ testScope.runTest {
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isFalse()
+ }
+
+ @Test
+ fun propertiesInitialized_onStartTrue() =
+ testScope.runTest {
+ // // collect sensorType to update fingerprintCallback before
+ // propertiesInitialized
+ // // is listened for
+ val sensorType by collectLastValue(underTest.sensorType)
+ runCurrent()
+ captureFingerprintCallback()
+
+ fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isTrue()
+ }
+
+ @Test
+ fun propertiesInitialized_updatedToTrue() =
+ testScope.runTest {
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isFalse()
+
+ captureFingerprintCallback()
+ fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+ assertThat(propertiesInitialized).isTrue()
+ }
+
+ private fun captureFingerprintCallback() {
+ verify(fingerprintManager)
+ .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index cb2d4e0..addbdb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -60,17 +60,19 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardRepository
- private val sceneInteractor = kosmos.sceneInteractor
- private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
- private val commandQueue = kosmos.fakeCommandQueue
- private val configRepository = kosmos.fakeConfigurationRepository
- private val bouncerRepository = kosmos.keyguardBouncerRepository
- private val shadeRepository = kosmos.shadeRepository
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val repository by lazy { kosmos.fakeKeyguardRepository }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+ private val commandQueue by lazy { kosmos.fakeCommandQueue }
+ private val configRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+
private val transitionState: MutableStateFlow<ObservableTransitionState> =
MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
- private val underTest = kosmos.keyguardInteractor
+
+ private val underTest by lazy { kosmos.keyguardInteractor }
@Before
fun setUp() {
@@ -275,6 +277,28 @@
}
@Test
+ fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() =
+ testScope.runTest {
+ val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+ configRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ 100
+ )
+ configRepository.onAnyConfigurationChange()
+
+ shadeRepository.setLegacyShadeExpansion(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ assertThat(keyguardTranslationY).isEqualTo(0f)
+ }
+
+ @Test
fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
testScope.runTest {
val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index bf0939c..99cccb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -19,9 +19,13 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
@@ -29,36 +33,74 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
-@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
val underTest = kosmos.keyguardTransitionInteractor
val repository = kosmos.fakeKeyguardTransitionRepository
val testScope = kosmos.testScope
+ private val sceneTransitions =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+
+ private val lsToGone =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ flowOf(Scenes.Lockscreen),
+ flowOf(0f),
+ false,
+ flowOf(false)
+ )
+
+ private val goneToLs =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ flowOf(0f),
+ false,
+ flowOf(false)
+ )
+
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+ }
+
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() =
testScope.runTest {
- val lockscreenToAodSteps by collectValues(underTest.transition(LOCKSCREEN, AOD))
- val aodToLockscreenSteps by collectValues(underTest.transition(AOD, LOCKSCREEN))
+ val lockscreenToAodSteps by
+ collectValues(underTest.transition(Edge.create(LOCKSCREEN, AOD)))
+ val aodToLockscreenSteps by
+ collectValues(underTest.transition(Edge.create(AOD, LOCKSCREEN)))
val steps = mutableListOf<TransitionStep>()
steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
@@ -482,6 +524,7 @@
}
@Test
+ @DisableSceneContainer
fun isInTransitionToState() =
testScope.runTest {
val results by collectValues(underTest.isInTransitionToState(GONE))
@@ -586,7 +629,7 @@
)
sendSteps(
- TransitionStep(DOZING, GONE, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
)
assertThat(results)
@@ -598,7 +641,7 @@
)
sendSteps(
- TransitionStep(DOZING, GONE, 0f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
)
assertThat(results)
@@ -610,7 +653,7 @@
)
sendSteps(
- TransitionStep(DOZING, GONE, 0f, FINISHED),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED),
)
assertThat(results)
@@ -623,9 +666,9 @@
)
sendSteps(
- TransitionStep(GONE, DOZING, 0f, STARTED),
- TransitionStep(GONE, DOZING, 0f, RUNNING),
- TransitionStep(GONE, DOZING, 1f, FINISHED),
+ TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, DOZING, 0f, RUNNING),
+ TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED),
)
assertThat(results)
@@ -638,8 +681,8 @@
)
sendSteps(
- TransitionStep(DOZING, GONE, 0f, STARTED),
- TransitionStep(DOZING, GONE, 0f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
)
assertThat(results)
@@ -1404,6 +1447,143 @@
)
}
+ @Test
+ @DisableSceneContainer
+ fun transition_no_conversion_with_flag_off() =
+ testScope.runTest {
+ val currentStates by
+ collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE)))
+
+ val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED)
+ sendSteps(sendStep1)
+
+ assertEquals(listOf(sendStep1), currentStates)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_with_flag_on() =
+ testScope.runTest {
+ val currentStates by
+ collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE)))
+
+ val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED)
+ sendSteps(sendStep1)
+
+ assertEquals(listOf<TransitionStep>(), currentStates)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_emits_values_with_sceneContainer_in_correct_state() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE)))
+ val currentStatesConverted by
+ collectValues(underTest.transition(Edge.create(LOCKSCREEN, UNDEFINED)))
+
+ sceneTransitions.value = lsToGone
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3)
+
+ assertEquals(listOf(sendStep1, sendStep2), currentStates)
+ assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_emits_nothing_with_sceneContainer_in_wrong_state() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE)))
+
+ sceneTransitions.value = goneToLs
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3)
+
+ assertEquals(listOf<TransitionStep>(), currentStates)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_emits_values_when_edge_within_lockscreen_scene() =
+ testScope.runTest {
+ val currentStates by
+ collectValues(underTest.transition(Edge.create(LOCKSCREEN, DOZING)))
+
+ sceneTransitions.value = goneToLs
+ val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
+ val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3)
+
+ assertEquals(listOf(sendStep1, sendStep2), currentStates)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_emits_values_with_null_edge_within_lockscreen_scene() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, null)))
+ val currentStatesReversed by
+ collectValues(underTest.transition(Edge.create(null, LOCKSCREEN)))
+
+ sceneTransitions.value = goneToLs
+ val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
+ val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+ val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+ assertEquals(listOf(sendStep1, sendStep2, sendStep3), currentStates)
+ assertEquals(listOf(sendStep4), currentStatesReversed)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_emits_values_with_null_edge_out_of_lockscreen_scene() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.transition(Edge.create(null, UNDEFINED)))
+ val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
+
+ sceneTransitions.value = lsToGone
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
+ val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+ assertEquals(listOf(sendStep1, sendStep2), currentStates)
+ assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_conversion_does_not_emit_with_null_edge_with_wrong_stl_state() =
+ testScope.runTest {
+ val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
+
+ sceneTransitions.value = goneToLs
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
+ val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+ sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+ assertEquals(listOf<TransitionStep>(), currentStatesMapped)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_null_edges_throw() =
+ testScope.runTest {
+ assertThrows(IllegalStateException::class.java) {
+ underTest.transition(Edge.create(null, null))
+ }
+ }
+
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 0ac7ff5..a0fed6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -23,11 +23,13 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
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.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
@@ -50,11 +52,14 @@
@Before
fun setUp() {
underTest =
- animationFlow.setup(
- duration = 1000.milliseconds,
- from = GONE,
- to = DREAMING,
- )
+ animationFlow
+ .setup(
+ duration = 1000.milliseconds,
+ edge = Edge.create(from = Scenes.Gone, to = DREAMING),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = DREAMING),
+ )
}
@Test(expected = IllegalArgumentException::class)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index d632936..7a9bd92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -87,6 +87,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
@@ -137,6 +138,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun scrimBehindAlpha_leaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
@@ -161,6 +163,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun showAllNotifications_isTrue_whenLeaveShadeOpen() =
testScope.runTest {
val showAllNotifications by
@@ -177,6 +180,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() =
testScope.runTest {
val showAllNotifications by
@@ -193,6 +197,7 @@
}
@Test
+ @BrokenWithSceneContainer(330311871)
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
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 838b2a7..20ffa33 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
@@ -30,6 +30,8 @@
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.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -37,7 +39,9 @@
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.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
@@ -49,6 +53,8 @@
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -75,6 +81,11 @@
private val viewState = ViewStateAccessor()
+ private val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+
companion object {
@JvmStatic
@Parameters(name = "{0}")
@@ -96,6 +107,7 @@
AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
)
}
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
}
@Test
@@ -309,6 +321,32 @@
}
@Test
+ @EnableSceneContainer
+ fun alpha_transitionToHub_isZero_scene_container() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Communal,
+ emptyFlow(),
+ emptyFlow(),
+ false,
+ emptyFlow()
+ )
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ testScope,
+ )
+
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @DisableSceneContainer
fun alpha_transitionToHub_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 58c6817..1c1fcc4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -18,8 +18,10 @@
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -30,11 +32,16 @@
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.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -58,6 +65,11 @@
private val keyguardRepository = kosmos.fakeKeyguardRepository
private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel
+ private val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+
companion object {
@JvmStatic
@Parameters(name = "{0}")
@@ -76,6 +88,7 @@
}
@Test
+ @BrokenWithSceneContainer(330311871)
fun deviceEntryParentViewAlpha_shadeExpanded() =
testScope.runTest {
val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
@@ -107,6 +120,17 @@
shadeExpanded(false)
runCurrent()
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Lockscreen,
+ toScene = Scenes.Bouncer,
+ emptyFlow(),
+ emptyFlow(),
+ false,
+ emptyFlow()
+ )
+ runCurrent()
// fade out
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
runCurrent()
@@ -132,7 +156,9 @@
): TransitionStep {
return TransitionStep(
from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.PRIMARY_BOUNCER,
+ to =
+ if (SceneContainerFlag.isEnabled) KeyguardState.UNDEFINED
+ else KeyguardState.PRIMARY_BOUNCER,
value = value,
transitionState = state,
ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d9224d7..365a7c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -27,12 +27,16 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.mockBroadcastDialogController
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
@@ -41,16 +45,16 @@
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -117,7 +121,7 @@
whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
.thenReturn(true)
- val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+ val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
val expandable = mock<Expandable>()
underTest.startClickIntent(expandable, clickIntent)
@@ -129,7 +133,7 @@
fun startClickIntent_hideOverLockscreen() {
whenever(keyguardStateController.isShowing).thenReturn(false)
- val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+ val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
val expandable = mock<Expandable>()
val activityController = mock<ActivityTransitionAnimator.Controller>()
whenever(expandable.activityTransitionController(any())).thenReturn(activityController)
@@ -146,7 +150,7 @@
whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
.thenReturn(true)
- val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+ val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
underTest.startDeviceIntent(deviceIntent)
@@ -159,7 +163,7 @@
whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
.thenReturn(true)
- val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }
+ val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(false) }
underTest.startDeviceIntent(deviceIntent)
@@ -170,7 +174,7 @@
fun startDeviceIntent_hideOverLockscreen() {
whenever(keyguardStateController.isShowing).thenReturn(false)
- val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+ val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
underTest.startDeviceIntent(deviceIntent)
@@ -211,6 +215,21 @@
)
}
+ @Test
+ fun removeMediaControl() {
+ val listener = mock<MediaDataProcessor.Listener>()
+ kosmos.mediaDataProcessor.addInternalListener(listener)
+
+ var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
+ kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
+
+ underTest.removeMediaControl(null, instanceId, 0L)
+ kosmos.fakeExecutor.advanceClockToNext()
+ kosmos.fakeExecutor.runAllReady()
+
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
+ }
+
companion object {
private const val USER_ID = 0
private const val KEY = "key"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 4226a9d..0551bfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -109,7 +109,7 @@
assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
- underTest.onAttached()
+ underTest.onReorderingAllowed()
mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index 5661bd3..9d8ec95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -51,8 +51,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
private val underTest = kosmos.notificationsShadeSceneViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784..02a8141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@
}
@Test
+ fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(null, intent, true)
+
+ verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+ }
+
+ @Test
fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 0000000..c41ce2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+ private val testUser = UserHandle.of(1)!!
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val testIntent = mock<Intent>()
+ private val qrCodeScannerController =
+ mock<QRCodeScannerController> {
+ whenever(intent).thenReturn(testIntent)
+ whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+ }
+ private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+ private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ private val underTest: QRCodeScannerTileDataInteractor =
+ QRCodeScannerTileDataInteractor(
+ testDispatcher,
+ scope.backgroundScope,
+ qrCodeScannerController,
+ )
+
+ @Test
+ fun availability_matchesController_cameraNotAvailable() =
+ scope.runTest {
+ val expectedAvailability = false
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun availability_matchesController_cameraIsAvailable() =
+ scope.runTest {
+ val expectedAvailability = true
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun data_matchesController() =
+ scope.runTest {
+ val captor = argumentCaptor<Callback>()
+ val lastData by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ verify(qrCodeScannerController).addCallback(captor.capture())
+ val callback = captor.value
+
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..312f180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+ val kosmos = Kosmos()
+ private val inputHandler = kosmos.qsTileIntentUserInputHandler
+ private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+ private val intent = mock<Intent>()
+
+ @Test
+ fun handleClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ intent
+ }
+ }
+
+ @Test
+ fun handleClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
new file mode 100644
index 0000000..d26a213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsQRCodeScannerTileConfig
+
+ private lateinit var mapper: QRCodeScannerTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ QRCodeScannerTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(
+ com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+ TestStubDrawable()
+ )
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun availableModel() {
+ val mockIntent = mock<Intent>()
+ val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.INACTIVE,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun temporarilyUnavailableModel() {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ context.getString(
+ com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createQRCodeScannerTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String?,
+ ): QSTileState {
+ val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
index c75e297..e3108ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
@@ -45,11 +45,11 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val sceneContainerStartable = kosmos.sceneContainerStartable
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
- private val underTest = kosmos.sceneBackInteractor
+ private val underTest by lazy { kosmos.sceneBackInteractor }
@Test
@EnableSceneContainer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 93465c8..677477d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -437,12 +437,12 @@
runCurrent()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
)
.isTrue()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
)
.isFalse()
@@ -450,12 +450,12 @@
runCurrent()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
)
.isFalse()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
)
.isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index e11a8f1..851b7b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,9 +52,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val sceneInteractor = kosmos.sceneInteractor
- private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
+ private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor }
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
new file mode 100644
index 0000000..8cb811d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.mockNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationMediaManagerTest : SysuiTestCase() {
+
+ private val KEY = "KEY"
+
+ private val kosmos = testKosmos()
+ private val visibilityProvider = kosmos.notificationVisibilityProvider
+ private val notifPipeline = kosmos.notifPipeline
+ private val notifCollection = kosmos.mockNotifCollection
+ private val dumpManager = kosmos.dumpManager
+ private val mediaDataManager = mock<MediaDataManager>()
+ private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+ private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>()
+
+ private lateinit var notificationMediaManager: NotificationMediaManager
+
+ @Before
+ fun setup() {
+ notificationMediaManager =
+ NotificationMediaManager(
+ context,
+ visibilityProvider,
+ notifPipeline,
+ notifCollection,
+ mediaDataManager,
+ dumpManager,
+ backgroundExecutor,
+ )
+
+ verify(mediaDataManager).addListener(listenerCaptor.capture())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_userInitiated_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, true)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() {
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection, never()).dismissNotification(any(), any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index c5d7e1f..2853264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -23,9 +23,8 @@
import android.app.NotificationManager.IMPORTANCE_LOW
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -44,25 +43,29 @@
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class ConversationCoordinatorTest : SysuiTestCase() {
// captured listeners and pluggables:
@@ -72,22 +75,20 @@
private lateinit var peopleComparator: NotifComparator
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+ private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+ private lateinit var peopleAlertingSection: NotifSection
+
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var conversationIconManager: ConversationIconManager
- @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
- @Mock private lateinit var channel: NotificationChannel
@Mock private lateinit var headerController: NodeController
- private lateinit var entry: NotificationEntry
- private lateinit var entryA: NotificationEntry
- private lateinit var entryB: NotificationEntry
private lateinit var coordinator: ConversationCoordinator
- @Rule @JvmField public val setFlagsRule = SetFlagsRule()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ peopleNotificationIdentifier =
+ PeopleNotificationIdentifierImpl(mock(), GroupMembershipManagerImpl())
coordinator =
ConversationCoordinator(
peopleNotificationIdentifier,
@@ -95,7 +96,6 @@
HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
headerController
)
- whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
@@ -110,49 +110,65 @@
if (!SortBySectionTimeFlag.isEnabled)
peopleComparator = peopleAlertingSectioner.comparator!!
- entry = NotificationEntryBuilder().setChannel(channel).build()
+ peopleAlertingSection = NotifSection(peopleAlertingSectioner, 0)
+ }
- val section = NotifSection(peopleAlertingSectioner, 0)
- entryA =
- NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
- entryB =
- NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
+ @Test
+ fun priorityPeopleSectionerClaimsOnlyImportantConversations() {
+ val sectioner = coordinator.priorityPeopleSectioner
+ assertTrue(sectioner.isInSection(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON)))
+ assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_FULL_PERSON)))
+ assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_PERSON)))
+ assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_NON_PERSON)))
+ assertFalse(sectioner.isInSection(NotificationEntryBuilder().build()))
}
@Test
fun testPromotesImportantConversations() {
- // only promote important conversations
- assertTrue(promoter.shouldPromoteToTopLevel(entry))
+ assertTrue(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON)))
+ assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_FULL_PERSON)))
+ assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_PERSON)))
+ assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_NON_PERSON)))
assertFalse(promoter.shouldPromoteToTopLevel(NotificationEntryBuilder().build()))
}
@Test
fun testPromotedImportantConversationsMakesSummaryUnimportant() {
- val altChildA = NotificationEntryBuilder().setTag("A").build()
- val altChildB = NotificationEntryBuilder().setTag("B").build()
- val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+ val importantChannel =
+ mock<NotificationChannel>().also {
+ whenever(it.isImportantConversation).thenReturn(true)
+ }
+ val otherChannel =
+ mock<NotificationChannel>().also {
+ whenever(it.isImportantConversation).thenReturn(false)
+ }
+ val importantChild =
+ makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel) }
+ val altChildA =
+ makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("A") }
+ val altChildB =
+ makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("B") }
+ val summary =
+ makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel).setId(2) }
val groupEntry =
GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
- .setChildren(listOf(entry, altChildA, altChildB))
+ .setChildren(listOf(importantChild, altChildA, altChildB))
.build()
- assertTrue(promoter.shouldPromoteToTopLevel(entry))
+ assertTrue(promoter.shouldPromoteToTopLevel(importantChild))
assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
- NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY)
- GroupEntryBuilder.getRawChildren(groupEntry).remove(entry)
- beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry))
+ NotificationEntryBuilder.setNewParent(importantChild, GroupEntry.ROOT_ENTRY)
+ GroupEntryBuilder.getRawChildren(groupEntry).remove(importantChild)
+ beforeRenderListListener.onBeforeRenderList(listOf(importantChild, groupEntry))
verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key)))
}
@Test
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
- val alertingEntry =
- NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
- .thenReturn(TYPE_PERSON)
+ val alertingEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_DEFAULT) }
// put alerting people notifications in this section
assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
@@ -162,10 +178,7 @@
@EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
fun testInAlertingPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
- val silentEntry =
- NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
- .thenReturn(TYPE_PERSON)
+ val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) }
// THEN put silent people notifications in alerting section
assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isTrue()
@@ -175,10 +188,7 @@
@DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
- val silentEntry =
- NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
- .thenReturn(TYPE_PERSON)
+ val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) }
// THEN put silent people notifications in this section
assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
@@ -191,18 +201,14 @@
@Test
fun testNotInPeopleSection() {
// GIVEN
- val entry =
- NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+ val entry = makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_LOW) }
val importantEntry =
- NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
- .thenReturn(TYPE_NON_PERSON)
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
- .thenReturn(TYPE_NON_PERSON)
+ makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_HIGH) }
// THEN - only put people notification either silent or alerting
- if (!SortBySectionTimeFlag.isEnabled)
+ if (!SortBySectionTimeFlag.isEnabled) {
assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
+ }
assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse()
}
@@ -210,22 +216,16 @@
fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
// GIVEN
val altChildA =
- NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
- val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
- val summary =
- NotificationEntryBuilder()
- .setId(2)
- .setImportance(IMPORTANCE_LOW)
- .setChannel(channel)
- .build()
+ makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("A").setImportance(IMPORTANCE_DEFAULT) }
+ val altChildB =
+ makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("B").setImportance(IMPORTANCE_LOW) }
+ val summary = makeEntryOfPeopleType(TYPE_PERSON) { setId(2).setImportance(IMPORTANCE_LOW) }
val groupEntry =
GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(listOf(altChildA, altChildB))
.build()
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
- .thenReturn(TYPE_PERSON)
// THEN
assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
@@ -233,10 +233,12 @@
@Test
@DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
fun testComparatorPutsImportantPeopleFirst() {
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
- .thenReturn(TYPE_IMPORTANT_PERSON)
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
- .thenReturn(TYPE_PERSON)
+ val entryA =
+ makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) {
+ setSection(peopleAlertingSection).setTag("A")
+ }
+ val entryB =
+ makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") }
// only put people notifications in this section
assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1)
@@ -245,10 +247,10 @@
@Test
@DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
fun testComparatorEquatesPeopleWithSameType() {
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
- .thenReturn(TYPE_PERSON)
- whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
- .thenReturn(TYPE_PERSON)
+ val entryA =
+ makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("A") }
+ val entryB =
+ makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") }
// only put people notifications in this section
assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0)
@@ -259,4 +261,23 @@
fun testNoSecondarySortForConversations() {
assertThat(peopleAlertingSectioner.comparator).isNull()
}
+
+ private fun makeEntryOfPeopleType(
+ @PeopleNotificationType type: Int,
+ buildBlock: NotificationEntryBuilder.() -> Unit = {}
+ ): NotificationEntry {
+ val channel: NotificationChannel = mock()
+ whenever(channel.isImportantConversation).thenReturn(type == TYPE_IMPORTANT_PERSON)
+ val entry =
+ NotificationEntryBuilder()
+ .updateRanking {
+ it.setIsConversation(type != TYPE_NON_PERSON)
+ it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null)
+ it.setChannel(channel)
+ }
+ .also(buildBlock)
+ .build()
+ assertEquals(type, peopleNotificationIdentifier.getPeopleNotificationType(entry))
+ return entry
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 50ae985..ce134e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -31,9 +31,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.compose.animation.scene.ObservableTransitionState;
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -75,7 +76,7 @@
import org.mockito.verification.VerificationMode;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@@ -86,6 +87,7 @@
@Mock private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
+ @Mock private SeenNotificationsInteractor mSeenNotificationsInteractor;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@@ -121,6 +123,7 @@
mHeadsUpManager,
mShadeAnimationInteractor,
mJavaAdapter,
+ mSeenNotificationsInteractor,
mStatusBarStateController,
mVisibilityLocationProvider,
mVisualStabilityProvider,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 1f0812d..ee9fd349 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -44,13 +44,4 @@
collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
assertThat(stackBounds).isEqualTo(bounds)
}
-
- @Test
- fun onStackBoundsChanged() =
- kosmos.testScope.runTest {
- underTest.onStackBoundsChanged(top = 5f, bottom = 500f)
- assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f)
- assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value)
- .isEqualTo(500f)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index f2ce745..82e2bb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
@@ -107,18 +108,25 @@
val testScope = kosmos.testScope
val configurationRepository
get() = kosmos.fakeConfigurationRepository
+
val keyguardRepository
get() = kosmos.fakeKeyguardRepository
+
val keyguardInteractor
get() = kosmos.keyguardInteractor
+
val keyguardRootViewModel
get() = kosmos.keyguardRootViewModel
+
val keyguardTransitionRepository
get() = kosmos.fakeKeyguardTransitionRepository
+
val shadeTestUtil
get() = kosmos.shadeTestUtil
+
val sharedNotificationContainerInteractor
get() = kosmos.sharedNotificationContainerInteractor
+
val largeScreenHeaderHelper
get() = kosmos.mockLargeScreenHeaderHelper
@@ -660,9 +668,6 @@
overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
- keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 2f)
- )
assertThat(maxNotifications).isEqualTo(10)
@@ -691,9 +696,6 @@
overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
- keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 2f)
- )
assertThat(maxNotifications).isEqualTo(10)
@@ -728,9 +730,6 @@
overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
- keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 2f)
- )
// -1 means No Limit
assertThat(maxNotifications).isEqualTo(-1)
@@ -823,6 +822,7 @@
}
@Test
+ @BrokenWithSceneContainer(330311871)
fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() =
testScope.runTest {
val viewState = ViewStateAccessor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
index 1cd12f0..7bc6948 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
@@ -35,6 +35,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
@@ -46,30 +47,32 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneContainerRepository = kosmos.sceneContainerRepository
- private val keyguardInteractor = kosmos.keyguardInteractor
+ lateinit var underTest: DozeServiceHost
- val underTest =
- kosmos.dozeServiceHost.apply {
- initialize(
- /* centralSurfaces = */ mock(),
- /* statusBarKeyguardViewManager = */ mock(),
- /* notificationShadeWindowViewController = */ mock(),
- /* ambientIndicationContainer = */ mock(),
- )
- }
+ @Before
+ fun setup() {
+ underTest =
+ kosmos.dozeServiceHost.apply {
+ initialize(
+ /* centralSurfaces = */ mock(),
+ /* statusBarKeyguardViewManager = */ mock(),
+ /* notificationShadeWindowViewController = */ mock(),
+ /* ambientIndicationContainer = */ mock(),
+ )
+ }
+ }
@Test
@EnableSceneContainer
fun startStopDozing() =
testScope.runTest {
- val isDozing by collectLastValue(keyguardInteractor.isDozing)
+ val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing)
// GIVEN a callback is set
val callback: DozeHost.Callback = mock()
underTest.addCallback(callback)
// AND we are on the lock screen
- sceneContainerRepository.changeScene(Scenes.Lockscreen)
+ kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen)
// AND dozing is not requested yet
assertThat(underTest.dozingRequested).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 675136c..a163ca0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -36,7 +36,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,19 +46,8 @@
private val kosmos = testKosmos()
- private lateinit var underTest: AudioVolumeInteractor
-
- @Before
- fun setup() {
- with(kosmos) {
- underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
-
- audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
-
- notificationsSoundPolicyRepository.updateNotificationPolicy()
- notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
- }
- }
+ private val underTest: AudioVolumeInteractor =
+ with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) }
@Test
fun setMuted_mutesStream() {
@@ -236,6 +224,55 @@
}
}
+ @Test
+ fun testReducingVolumeToMin_mutes() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ runCurrent()
+
+ underTest.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.isMuted).isTrue()
+ }
+ }
+
+ @Test
+ fun testIncreasingVolumeFromMin_unmutes() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ audioRepository.setMuted(audioStream, true)
+ audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ underTest.setVolume(audioStream, audioStreamModel!!.maxVolume)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.isMuted).isFalse()
+ }
+ }
+
+ @Test
+ fun testUnmutingMinVolume_increasesVolume() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ audioRepository.setMuted(audioStream, true)
+ audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume)
+ }
+ }
+
private companion object {
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
index 64c9429..46df0c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
@@ -16,17 +16,16 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
-import android.os.Handler
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.shared.model.filterData
import com.android.systemui.volume.remoteMediaController
@@ -55,12 +54,7 @@
listOf(localMediaController, remoteMediaController)
)
- underTest =
- MediaDeviceSessionInteractor(
- testScope.testScheduler,
- Handler(TestableLooper.get(kosmos.testCase).looper),
- mediaControllerRepository,
- )
+ underTest = mediaDeviceSessionInteractor
}
}
diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
index a751f58..370677ac 100644
--- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/material_dynamic_neutral20" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/hub_handle.xml
similarity index 77%
copy from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
copy to packages/SystemUI/res/drawable/hub_handle.xml
index bdd6270..8bc276f 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/hub_handle.xml
@@ -1,5 +1,5 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorAccent" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="4dp" />
+ <solid android:color="#FFFFFF" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
similarity index 90%
rename from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
rename to packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
index bdd6270..b9a4cbf 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?android:attr/colorAccent" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index f644584f..01b9f7e 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,8 +26,8 @@
android:paddingVertical="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
- app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+ app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
+ app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
@@ -35,8 +35,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
@@ -47,6 +47,7 @@
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
@@ -62,8 +63,8 @@
android:paddingTop="24dp"
android:fadeScrollbars="false"
app:layout_constraintBottom_toTopOf="@+id/button_bar"
- app:layout_constraintEnd_toStartOf="@+id/midGuideline"
- app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintRight_toLeftOf="@+id/midGuideline"
+ app:layout_constraintLeft_toLeftOf="@id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -88,7 +89,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:paddingLeft="16dp"
+ android:paddingStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
@@ -208,6 +209,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
<androidx.constraintlayout.widget.Guideline
@@ -215,6 +217,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
<androidx.constraintlayout.widget.Guideline
@@ -222,6 +225,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_begin="406dp" />
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 46b8e46..05f6fae 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -229,6 +229,7 @@
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 4d2310a..0bbe73c 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -27,7 +27,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:ellipsize="end"
android:maxLines="2"
android:visibility="invisible"
@@ -41,7 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:text="@string/cancel"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
@@ -54,7 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@@ -66,7 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginRight="24dp"
+ android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/biometric_dialog_confirm"
@@ -81,7 +81,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginRight="24dp"
+ android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/biometric_dialog_try_again"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index d51fe58..fa4d9a8 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -222,6 +222,7 @@
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 76d10cc..a598007 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -136,7 +136,8 @@
<TextView
android:id="@+id/bluetooth_auto_on_toggle_title"
android:layout_width="0dp"
- android:layout_height="68dp"
+ android:layout_height="wrap_content"
+ android:minHeight="68dp"
android:layout_marginBottom="20dp"
android:maxLines="2"
android:ellipsize="end"
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 19fb874..4234fca5 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -21,6 +21,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/glanceable_hub_handle"
+ android:layout_width="4dp"
+ android:layout_height="220dp"
+ android:layout_centerVertical="true"
+ android:layout_marginEnd="12dp"
+ android:background="@drawable/hub_handle"
+ android:visibility="gone"
+ android:contentDescription="UI indicator for swiping open the glanceable hub"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dream_overlay_content"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
similarity index 66%
rename from packages/SystemUI/res/layout/ongoing_call_chip.xml
rename to packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 6a0217ec..a33be12 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -17,43 +17,45 @@
the chip. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/ongoing_call_chip"
+ android:id="@+id/ongoing_activity_chip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer
- android:id="@+id/ongoing_call_chip_background"
+ <!-- TODO(b/332662551): Update this content description when this supports more than just
+ phone calls. -->
+ <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+ android:id="@+id/ongoing_activity_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_gravity="center_vertical"
android:gravity="center"
- android:background="@drawable/ongoing_call_chip_bg"
- android:paddingStart="@dimen/ongoing_call_chip_side_padding"
- android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
+ android:background="@drawable/ongoing_activity_chip_bg"
+ android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
+ android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
android:contentDescription="@string/ongoing_phone_call_content_description"
android:minWidth="@dimen/min_clickable_item_size"
>
<ImageView
android:src="@*android:drawable/ic_phone"
- android:layout_width="@dimen/ongoing_call_chip_icon_size"
- android:layout_height="@dimen/ongoing_call_chip_icon_size"
+ android:layout_width="@dimen/ongoing_activity_chip_icon_size"
+ android:layout_height="@dimen/ongoing_activity_chip_icon_size"
android:tint="?android:attr/colorPrimary"
/>
- <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
- android:id="@+id/ongoing_call_chip_time"
+ <com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+ android:id="@+id/ongoing_activity_chip_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center|start"
- android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
+ android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textColor="?android:attr/colorPrimary"
/>
- </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer>
+ </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 2cb4b02..49d3a8e 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -135,11 +135,12 @@
android:id="@+id/screenshot_scrollable_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:clipToOutline="true"
android:scaleType="matrix"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
- android:elevation="7dp"/>
+ android:elevation="3dp"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
@@ -170,6 +171,13 @@
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
+ android:id="@+id/screenshot_scrolling_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:clickable="true"
+ android:importantForAccessibility="no"/>
+ <ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 452bc31..4247c7e 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -99,7 +99,7 @@
android:gravity="center_vertical|start"
/>
- <include layout="@layout/ongoing_call_chip" />
+ <include layout="@layout/ongoing_activity_chip" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 517b44f..9cd7d16 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -914,8 +914,8 @@
<dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen>
<!-- Width and height used to filter widgets displayed in the communal widget picker -->
- <dimen name="communal_widget_picker_desired_width">424dp</dimen>
- <dimen name="communal_widget_picker_desired_height">282dp</dimen>
+ <dimen name="communal_widget_picker_desired_width">360dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">240dp</dimen>
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
@@ -1713,12 +1713,12 @@
<dimen name="wallet_button_horizontal_padding">24dp</dimen>
<dimen name="wallet_button_vertical_padding">8dp</dimen>
- <!-- Ongoing call chip -->
- <dimen name="ongoing_call_chip_side_padding">12dp</dimen>
- <dimen name="ongoing_call_chip_icon_size">16dp</dimen>
+ <!-- Ongoing activity chip -->
+ <dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+ <dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
<!-- The padding between the icon and the text. -->
- <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
- <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+ <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b5ec5b2..aecc906 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -384,6 +384,8 @@
<!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_logo">App logo</string>
+ <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] -->
+ <string-array name="biometric_dialog_package_names_for_logo_with_overrides" />
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
<!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 8ac1de8..c33b7ce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -99,7 +99,7 @@
final float startScale = sourceRectHint.width() <= sourceRectHint.height()
? (float) destinationBounds.width() / sourceBounds.width()
: (float) destinationBounds.height() / sourceBounds.height();
- scale = (1 - progress) * startScale + progress * endScale;
+ scale = Math.min((1 - progress) * startScale + progress * endScale, 1.0f);
}
final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index d191a3c..d5bc10a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -65,7 +65,7 @@
/**
* Sent when some system ui state changes.
*/
- void onSystemUiStateChanged(int stateFlags) = 16;
+ void onSystemUiStateChanged(long stateFlags) = 16;
/**
* Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 94b6fd4..090033d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -162,5 +162,10 @@
oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier, boolean haptic)
= 55;
- // Next id = 56
+ /**
+ * Notifies to toggle quick settings panel.
+ */
+ oneway void toggleQuickSettingsPanel() = 56;
+
+ // Next id = 57
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 69aa909..b4377ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -22,7 +22,7 @@
import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
-import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.content.Context;
import android.content.res.Resources;
import android.view.ViewConfiguration;
@@ -51,92 +51,94 @@
// Overview is disabled, either because the device is in lock task mode, or because the device
// policy has disabled the feature
- public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0;
+ public static final long SYSUI_STATE_SCREEN_PINNING = 1L << 0;
// The navigation bar is hidden due to immersive mode
- public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1;
+ public static final long SYSUI_STATE_NAV_BAR_HIDDEN = 1L << 1;
// The notification panel is expanded and interactive (either locked or unlocked), and the
// quick settings is not expanded
- public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2;
+ public static final long SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1L << 2;
// The keyguard bouncer is showing
- public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3;
+ public static final long SYSUI_STATE_BOUNCER_SHOWING = 1L << 3;
// The navigation bar a11y button should be shown
- public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4;
+ public static final long SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1L << 4;
// The navigation bar a11y button shortcut is available
- public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5;
+ public static final long SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1L << 5;
// The keyguard is showing and not occluded
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1 << 6;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1L << 6;
// The recents feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_OVERVIEW_DISABLED = 1 << 7;
+ public static final long SYSUI_STATE_OVERVIEW_DISABLED = 1L << 7;
// The home feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_HOME_DISABLED = 1 << 8;
+ public static final long SYSUI_STATE_HOME_DISABLED = 1L << 8;
// The keyguard is showing, but occluded
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1L << 9;
// The search feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10;
+ public static final long SYSUI_STATE_SEARCH_DISABLED = 1L << 10;
// The notification panel is expanded and interactive (either locked or unlocked), and quick
// settings is expanded.
- public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
+ public static final long SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1L << 11;
// Winscope tracing is enabled
- public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
+ public static final long SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1L << 12;
// The Assistant gesture should be constrained. It is up to the launcher implementation to
// decide how to constrain it
- public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
+ public static final long SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1L << 13;
// The bubble stack is expanded. This means that the home gesture should be ignored, since a
// swipe up is an attempt to close the bubble stack, but that the back gesture should remain
// enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
// stack.
- public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
+ public static final long SYSUI_STATE_BUBBLES_EXPANDED = 1L << 14;
// A SysUI dialog is showing.
- public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
+ public static final long SYSUI_STATE_DIALOG_SHOWING = 1L << 15;
// The one-handed mode is active
- public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
+ public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16;
// Allow system gesture no matter the system bar(s) is visible or not
- public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17;
+ public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17;
// The IME is showing
- public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
+ public static final long SYSUI_STATE_IME_SHOWING = 1L << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
- public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
+ public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19;
// ImeSwitcher is showing
- public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20;
+ public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20;
// Device dozing/AOD state
- public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
+ public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21;
// The home feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+ public static final long SYSUI_STATE_BACK_DISABLED = 1L << 22;
// The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
- public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+ public static final long SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1L << 23;
// The voice interaction session window is showing
- public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
+ public static final long SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1L << 25;
// Freeform windows are showing in desktop mode
- public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
+ public static final long SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1L << 26;
// Device dreaming state
- public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
+ public static final long SYSUI_STATE_DEVICE_DREAMING = 1L << 27;
// Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle).
// Note that the device is awake on while waking up on, but not while going to sleep.
- public static final int SYSUI_STATE_AWAKE = 1 << 28;
+ public static final long SYSUI_STATE_AWAKE = 1L << 28;
// Whether the device is currently transitioning between awake/asleep indicated by
// SYSUI_STATE_AWAKE.
- public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
+ public static final long SYSUI_STATE_WAKEFULNESS_TRANSITION = 1L << 29;
// The notification panel expansion fraction is > 0
- public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+ public static final long SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1L << 30;
// When keyguard will be dismissed but didn't start animation yet
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1L << 31;
+ // Physical keyboard shortcuts helper is showing
+ public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
- public static final int SYSUI_STATE_WAKEFULNESS_MASK =
+ public static final long SYSUI_STATE_WAKEFULNESS_MASK =
SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION;
// Mirroring the WakefulnessLifecycle#Wakefulness states
- public static final int WAKEFULNESS_ASLEEP = 0;
- public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
- public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
- public static final int WAKEFULNESS_WAKING =
+ public static final long WAKEFULNESS_ASLEEP = 0;
+ public static final long WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
+ public static final long WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
+ public static final long WAKEFULNESS_WAKING =
SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
// Whether the back gesture is allowed (or ignored) by the Shade
public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
@Retention(RetentionPolicy.SOURCE)
- @IntDef({SYSUI_STATE_SCREEN_PINNING,
+ @LongDef({SYSUI_STATE_SCREEN_PINNING,
SYSUI_STATE_NAV_BAR_HIDDEN,
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -167,10 +169,11 @@
SYSUI_STATE_WAKEFULNESS_TRANSITION,
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+ SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
})
public @interface SystemUiStateFlags {}
- public static String getSystemUiStateString(int flags) {
+ public static String getSystemUiStateString(long flags) {
StringJoiner str = new StringJoiner("|");
if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) {
str.add("screen_pinned");
@@ -265,6 +268,9 @@
if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
str.add("keygrd_going_away");
}
+ if ((flags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0) {
+ str.add("shortcut_helper_showing");
+ }
return str.toString();
}
@@ -285,13 +291,13 @@
* Returns whether the specified sysui state is such that the assistant gesture should be
* disabled.
*/
- public static boolean isAssistantGestureDisabled(int sysuiStateFlags) {
+ public static boolean isAssistantGestureDisabled(long sysuiStateFlags) {
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
}
// Disable when in quick settings, screen pinning, immersive, the bouncer is showing,
// or search is disabled
- int disableFlags = SYSUI_STATE_SCREEN_PINNING
+ long disableFlags = SYSUI_STATE_SCREEN_PINNING
| SYSUI_STATE_NAV_BAR_HIDDEN
| SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_SEARCH_DISABLED
@@ -313,7 +319,7 @@
* Returns whether the specified sysui state is such that the back gesture should be
* disabled.
*/
- public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
+ public static boolean isBackGestureDisabled(long sysuiStateFlags, boolean forTrackpad) {
// Always allow when the bouncer/global actions/voice session is showing (even on top of
// the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -328,9 +334,9 @@
return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
}
- private static int getBackGestureDisabledMask(boolean forTrackpad) {
+ private static long getBackGestureDisabledMask(boolean forTrackpad) {
// Disable when in immersive, or the notifications are interactive
- int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+ long disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
if (!forTrackpad) {
disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f33acf2..3f3bb0b 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -544,10 +545,10 @@
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
merge(
- keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step ->
- step.copy(value = 1f - step.value)
+ keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map {
+ it.copy(value = 1f - it.value)
},
- keyguardTransitionInteractor.transition(LOCKSCREEN, AOD),
+ keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)),
).filter {
it.transitionState != TransitionState.FINISHED
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 905a98c..42838ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -326,7 +326,8 @@
}
if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+ "KeyguardSecurityContainerController#finish");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index 9c7fc9d..9ef9938 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -23,8 +23,7 @@
import android.view.GestureDetector;
import android.view.MotionEvent;
-import androidx.annotation.NonNull;
-
+import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -38,34 +37,29 @@
*/
public class ShadeTouchHandler implements TouchHandler {
private final Optional<CentralSurfaces> mSurfaces;
+ private final ShadeViewController mShadeViewController;
private final int mInitiationHeight;
- /**
- * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
- */
- private Boolean mCapture;
-
@Inject
ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+ ShadeViewController shadeViewController,
@Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
mSurfaces = centralSurfaces;
+ mShadeViewController = shadeViewController;
mInitiationHeight = initiationHeight;
}
@Override
public void onSessionStart(TouchSession session) {
- if (mSurfaces.isEmpty()) {
+ if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
session.pop();
return;
}
- session.registerCallback(() -> mCapture = null);
-
session.registerInputListener(ev -> {
+ mShadeViewController.handleExternalTouch((MotionEvent) ev);
+
if (ev instanceof MotionEvent) {
- if (mCapture != null && mCapture) {
- mSurfaces.get().handleExternalShadeWindowTouch((MotionEvent) ev);
- }
if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
session.pop();
}
@@ -74,25 +68,15 @@
session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
@Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
- if (mCapture == null) {
- // Only capture swipes that are going downwards.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
- if (mCapture) {
- // Send the initial touches over, as the input listener has already
- // processed these touches.
- mSurfaces.get().handleExternalShadeWindowTouch(e1);
- mSurfaces.get().handleExternalShadeWindowTouch(e2);
- }
- }
- return mCapture;
+ return true;
}
@Override
- public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
- return mCapture;
+ return true;
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b69e196..d6d40f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1249,6 +1249,8 @@
}
mCurrentDialog = newDialog;
+ // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+ // removed.
if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
cancelIfOwnerIsNotInForeground();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9816896..298b87d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -32,11 +32,18 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
@@ -131,6 +138,7 @@
override fun onUnlockedChanged() {
updatePauseAuth()
}
+
override fun onLaunchTransitionFadingAwayChanged() {
launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
updatePauseAuth()
@@ -211,7 +219,10 @@
suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor
- .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD)
+ .transition(
+ edge = Edge.create(Scenes.Bouncer, AOD),
+ edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD)
+ )
.collect { transitionStep ->
view.onDozeAmountChanged(
transitionStep.value,
@@ -225,8 +236,7 @@
@VisibleForTesting
suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
- transitionStep ->
+ transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep ->
view.onDozeAmountChanged(
transitionStep.value,
transitionStep.value,
@@ -239,23 +249,21 @@
@VisibleForTesting
suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor
- .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
- .collect { transitionStep ->
- view.onDozeAmountChanged(
- transitionStep.value,
- transitionStep.value,
- UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
- )
- }
+ transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect {
+ transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+ )
+ }
}
}
@VisibleForTesting
suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
- transitionStep ->
+ transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep ->
view.onDozeAmountChanged(
1f - transitionStep.value,
1f - transitionStep.value,
@@ -268,8 +276,7 @@
@VisibleForTesting
suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD).collect {
- transitionStep ->
+ transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep ->
view.onDozeAmountChanged(
transitionStep.value,
transitionStep.value,
@@ -282,14 +289,18 @@
@VisibleForTesting
suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor.transition(KeyguardState.GONE, KeyguardState.AOD).collect {
- transitionStep ->
- view.onDozeAmountChanged(
- transitionStep.value,
- transitionStep.value,
- ANIMATE_APPEAR_ON_SCREEN_OFF,
+ transitionInteractor
+ .transition(
+ edge = Edge.create(Scenes.Gone, AOD),
+ edgeWithoutSceneContainer = Edge.create(GONE, AOD)
)
- }
+ .collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATE_APPEAR_ON_SCREEN_OFF,
+ )
+ }
}
}
@@ -298,13 +309,10 @@
return scope.launch {
transitionInteractor.dozeAmountTransition.collect { transitionStep ->
if (
- transitionStep.from == KeyguardState.AOD &&
+ transitionStep.from == AOD &&
transitionStep.transitionState == TransitionState.CANCELED
) {
- if (
- transitionInteractor.startedKeyguardTransitionStep.first().to !=
- KeyguardState.AOD
- ) {
+ if (transitionInteractor.startedKeyguardTransitionStep.first().to != AOD) {
// If the next started transition isn't transitioning back to AOD, force
// doze amount to be 0f (as if the transition to the lockscreen completed).
view.onDozeAmountChanged(
@@ -557,6 +565,7 @@
private fun updateScaleFactor() {
udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
}
+
companion object {
const val TAG = "UdfpsKeyguardViewController"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 20e81c2..14d8caf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics.dagger
+import android.content.Context
import android.content.res.Resources
import com.android.internal.R
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
@@ -111,6 +113,9 @@
@Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
@Provides
+ fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
+
+ @Provides
@SysUISingleton
fun providesOverlapDetector(): OverlapDetector {
val selectedOption =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 40d38dd..6b61adc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -30,10 +30,10 @@
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -42,6 +42,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -52,7 +53,7 @@
*/
interface FingerprintPropertyRepository {
/** Whether the fingerprint properties have been initialized yet. */
- val propertiesInitialized: StateFlow<Boolean>
+ val propertiesInitialized: Flow<Boolean>
/** The id of fingerprint sensor. */
val sensorId: Flow<Int>
@@ -110,14 +111,8 @@
initialValue = UNINITIALIZED_PROPS,
)
- override val propertiesInitialized: StateFlow<Boolean> =
- props
- .map { it != UNINITIALIZED_PROPS }
- .stateIn(
- applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = props.value != UNINITIALIZED_PROPS,
- )
+ override val propertiesInitialized: Flow<Boolean> =
+ props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) }
override val sensorId: Flow<Int> = props.map { it.sensorId }
@@ -141,7 +136,7 @@
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
- private val UNINITIALIZED_PROPS =
+ val UNINITIALIZED_PROPS =
FingerprintSensorPropertiesInternal(
-2 /* sensorId */,
SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 3112b67..d5b450d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -46,7 +46,7 @@
displayStateInteractor: DisplayStateInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
- val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+ val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized
val isUdfps: StateFlow<Boolean> =
repository.sensorType
.map { it.isUdfps() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 6f079e2..4f96c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.content.ComponentName
import android.graphics.Bitmap
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
@@ -43,6 +44,9 @@
val logoBitmap: Bitmap? = info.logoBitmap
val logoDescription: String? = info.logoDescription
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
+ val componentNameForConfirmDeviceCredentialActivity: ComponentName? =
+ info.componentNameForConfirmDeviceCredentialActivity
+ val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication
}
/** Prompt using a credential (pin, pattern, password). */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index f0969ed..13ea3f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -199,29 +199,32 @@
iconParams.leftMargin = position.left
mediumConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
mediumConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
mediumConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
position.left
)
- smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END)
+ smallConstraintSet.clear(
+ R.id.biometric_icon,
+ ConstraintSet.RIGHT
+ )
smallConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
smallConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
position.left
)
}
@@ -252,32 +255,32 @@
iconParams.rightMargin = position.right
mediumConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
mediumConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
mediumConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
position.right
)
smallConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
smallConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
smallConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
position.right
)
}
@@ -383,15 +386,15 @@
// Move all content to other panel
flipConstraintSet.connect(
R.id.scrollView,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
R.id.midGuideline,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
flipConstraintSet.connect(
R.id.scrollView,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
R.id.rightGuideline,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index a8c5976..156ec6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -16,7 +16,10 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.app.ActivityTaskManager
+import android.content.ComponentName
import android.content.Context
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Rect
@@ -26,18 +29,22 @@
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
+import android.os.UserHandle
import android.util.Log
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.bpTalkback
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.Utils.isSystem
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -71,7 +78,9 @@
@Application private val context: Context,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val biometricStatusInteractor: BiometricStatusInteractor,
- private val udfpsUtils: UdfpsUtils
+ private val udfpsUtils: UdfpsUtils,
+ private val iconProvider: IconProvider,
+ private val activityTaskManager: ActivityTaskManager,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -499,14 +508,7 @@
!(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else ->
- try {
- val info = context.getApplicationInfo(it.opPackageName)
- context.packageManager.getApplicationIcon(info)
- } catch (e: Exception) {
- Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
- null
- }
+ else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -517,15 +519,8 @@
.map {
when {
!(customBiometricPrompt() && constraintBp()) || it == null -> ""
- it.logoDescription != null -> it.logoDescription
- else ->
- try {
- val info = context.getApplicationInfo(it.opPackageName)
- context.packageManager.getApplicationLabel(info).toString()
- } catch (e: Exception) {
- Log.w(TAG, "Cannot find name for package " + it.opPackageName, e)
- ""
- }
+ !it.logoDescription.isNullOrEmpty() -> it.logoDescription
+ else -> context.getUserBadgedLabel(it, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -926,15 +921,109 @@
}
companion object {
- private const val TAG = "PromptViewModel"
+ const val TAG = "PromptViewModel"
}
}
-private fun Context.getApplicationInfo(packageName: String): ApplicationInfo =
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
+private fun Context.getUserBadgedIcon(
+ prompt: BiometricPromptRequest.Biometric,
+ iconProvider: IconProvider,
+ activityTaskManager: ActivityTaskManager
+): Drawable? {
+ var icon: Drawable? = null
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
+ val activityInfo = getActivityInfo(componentName)
+ icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
+ }
+ if (icon == null) {
+ val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+ if (appInfo == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ return null
+ } else {
+ icon = packageManager.getApplicationIcon(appInfo)
+ }
+ }
+ return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
+}
+
+private fun Context.getUserBadgedLabel(
+ prompt: BiometricPromptRequest.Biometric,
+ activityTaskManager: ActivityTaskManager
+): String {
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+ return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ ""
+ } else {
+ packageManager
+ .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
+ .toString()
+ }
+}
+
+private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
+ activityTaskManager: ActivityTaskManager
+): ComponentName? {
+ val topActivity: ComponentName? = activityTaskManager.getTasks(1).firstOrNull()?.topActivity
+ return when {
+ componentNameForConfirmDeviceCredentialActivity != null ->
+ componentNameForConfirmDeviceCredentialActivity
+ topActivity?.packageName.contentEquals(opPackageName) -> topActivity
+ else -> {
+ Log.w(PromptViewModel.TAG, "Top activity $topActivity is not the client $opPackageName")
+ null
+ }
+ }
+}
+
+private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
+ context: Context,
+ componentNameForLogo: ComponentName?
+): ApplicationInfo? {
+ val packageName =
+ when {
+ componentNameForLogo != null -> componentNameForLogo.packageName
+ // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+ // removed.
+ // This is being consistent with the check in [AuthController.showDialog()].
+ allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName
+ else -> null
+ }
+ return if (packageName == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
+ null
+ } else {
+ context.getApplicationInfo(packageName)
+ }
+}
+
+private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean {
+ return resources
+ .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides)
+ .find { componentName.packageName.contentEquals(it) } != null
+}
+
+private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
+ try {
+ packageManager.getActivityInfo(componentName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find activity info for $opPackageName", e)
+ null
+ }
+
+private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? =
+ try {
+ packageManager.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
+ null
+ }
/** How the fingerprint sensor was started for the prompt. */
enum class FingerprintStartMode {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index dd8c0df..911145b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -204,11 +204,7 @@
isEnabled: Boolean,
@StringRes infoResId: Int
) {
- getAutoOnToggle(dialog).apply {
- isChecked = isEnabled
- setEnabled(true)
- alpha = ENABLED_ALPHA
- }
+ getAutoOnToggle(dialog).isChecked = isEnabled
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
}
@@ -236,12 +232,8 @@
}
getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
- getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked ->
+ getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked ->
mutableBluetoothAutoOnToggle.value = isChecked
- view.apply {
- isEnabled = false
- alpha = DISABLED_ALPHA
- }
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED)
}
}
@@ -427,8 +419,7 @@
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
- const val ACTION_AUDIO_SHARING =
- "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
+ const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
index c018ecb..0544a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +68,18 @@
user.id
)
?.let { PolicyRestriction.Restricted(it) }
- ?: PolicyRestriction.NoRestriction
+ ?: if (
+ enforceBrightnessBaseUserRestriction() &&
+ userRestrictionChecker.hasBaseUserRestriction(
+ applicationContext,
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+ user.id
+ )
+ ) {
+ PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+ } else {
+ PolicyRestriction.NoRestriction
+ }
}
.flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index c1be37a..a51d8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -23,7 +23,6 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
@@ -32,6 +31,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformSlider
import com.android.systemui.brightness.shared.GammaBrightness
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
@@ -107,10 +107,13 @@
viewModel: BrightnessSliderViewModel,
modifier: Modifier = Modifier,
) {
- val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0)
+ val gamma: Int by
+ viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0)
val coroutineScope = rememberCoroutineScope()
val restriction by
- viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction)
+ viewModel.policyRestriction.collectAsStateWithLifecycle(
+ initialValue = PolicyRestriction.NoRestriction
+ )
BrightnessSlider(
gammaValue = gamma,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 5653bc2..2eca02c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -31,9 +31,12 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -88,6 +91,8 @@
private final JavaAdapter mJavaAdapter;
private final SystemClock mSystemClock;
private final Lazy<SelectedUserInteractor> mUserInteractor;
+ private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
+ private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractor;
private int mState;
private boolean mShowingAod;
@@ -170,7 +175,9 @@
JavaAdapter javaAdapter,
SystemClock systemClock,
Lazy<SelectedUserInteractor> userInteractor,
- Lazy<CommunalInteractor> communalInteractorLazy) {
+ Lazy<CommunalInteractor> communalInteractorLazy,
+ Lazy<DeviceEntryInteractor> deviceEntryInteractor,
+ Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -186,6 +193,8 @@
mSystemClock = systemClock;
mUserInteractor = userInteractor;
mCommunalInteractorLazy = communalInteractorLazy;
+ mDeviceEntryInteractor = deviceEntryInteractor;
+ mSceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor;
}
@Override
@@ -196,7 +205,18 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mDeviceEntryInteractor.get().isDeviceEntered(),
+ this::isDeviceEnteredChanged
+ );
+ mJavaAdapter.alwaysCollectFlow(
+ mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion(),
+ this::isInvisibleDueToOcclusionChanged
+ );
+ } else {
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+ }
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
@@ -216,6 +236,14 @@
mDockManager.addListener(mDockEventListener);
}
+ public void isDeviceEnteredChanged(boolean unused) {
+ updateSensorRegistration();
+ }
+
+ public void isInvisibleDueToOcclusionChanged(boolean unused) {
+ updateSensorRegistration();
+ }
+
@Override
public void onSuccessfulUnlock() {
logDebug("REAL: onSuccessfulUnlock");
@@ -302,7 +330,7 @@
@Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")");
- if (!mKeyguardStateController.isShowing()) {
+ if (!isKeyguardShowing()) {
avoidGesture();
return;
}
@@ -402,8 +430,8 @@
final boolean isKeyguard = mState == StatusBarState.KEYGUARD;
final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE
- && mKeyguardStateController.isShowing()
- && mKeyguardStateController.isOccluded();
+ && isKeyguardShowing()
+ && isKeyguardOccluded();
return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard);
}
@@ -447,6 +475,32 @@
mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
}
+ /**
+ * Returns {@code true} if the keyguard is showing (whether or not the screen is on, whether or
+ * not an activity is occluding the keyguard, and whether or not the shade is open on top of the
+ * keyguard), or {@code false} if the user has dismissed the keyguard by authenticating or
+ * swiping up.
+ */
+ private boolean isKeyguardShowing() {
+ if (SceneContainerFlag.isEnabled()) {
+ return !mDeviceEntryInteractor.get().isDeviceEntered().getValue();
+ } else {
+ return mKeyguardStateController.isShowing();
+ }
+ }
+
+ /**
+ * Returns {@code true} if there is an activity display on top of ("occluding") the keyguard, or
+ * {@code false} if an activity is not occluding the keyguard (including if the keyguard is not
+ * showing at all).
+ */
+ private boolean isKeyguardOccluded() {
+ if (SceneContainerFlag.isEnabled()) {
+ return mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion().getValue();
+ } else {
+ return mKeyguardStateController.isOccluded();
+ }
+ }
static void logDebug(String msg) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 8efc66d..ba236ba 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -66,11 +66,11 @@
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance;
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel;
-import java.util.ArrayList;
-
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
+import java.util.ArrayList;
+
/**
* Handles the visual elements and animations for the clipboard overlay.
*/
@@ -109,6 +109,7 @@
private View mDismissButton;
private LinearLayout mActionContainer;
private ClipboardOverlayCallbacks mClipboardCallbacks;
+ private ActionButtonViewBinder mActionButtonViewBinder = new ActionButtonViewBinder();
public ClipboardOverlayView(Context context) {
this(context, null);
@@ -152,14 +153,15 @@
private void bindDefaultActionChips() {
if (screenshotShelfUi2()) {
- ActionButtonViewBinder.INSTANCE.bind(mRemoteCopyChip,
+ mActionButtonViewBinder.bind(mRemoteCopyChip,
ActionButtonViewModel.Companion.withNextId(
new ActionButtonAppearance(
Icon.createWithResource(mContext,
R.drawable.ic_baseline_devices_24).loadDrawable(
mContext),
null,
- mContext.getString(R.string.clipboard_send_nearby_description)),
+ mContext.getString(R.string.clipboard_send_nearby_description),
+ true),
new Function0<>() {
@Override
public Unit invoke() {
@@ -169,12 +171,14 @@
return null;
}
}));
- ActionButtonViewBinder.INSTANCE.bind(mShareChip,
+ mActionButtonViewBinder.bind(mShareChip,
ActionButtonViewModel.Companion.withNextId(
new ActionButtonAppearance(
Icon.createWithResource(mContext,
R.drawable.ic_screenshot_share).loadDrawable(mContext),
- null, mContext.getString(com.android.internal.R.string.share)),
+ null,
+ mContext.getString(com.android.internal.R.string.share),
+ true),
new Function0<>() {
@Override
public Unit invoke() {
@@ -512,9 +516,9 @@
private View constructShelfActionChip(RemoteAction action, Runnable onFinish) {
View chip = LayoutInflater.from(mContext).inflate(
R.layout.shelf_action_chip, mActionContainer, false);
- ActionButtonViewBinder.INSTANCE.bind(chip, ActionButtonViewModel.Companion.withNextId(
+ mActionButtonViewBinder.bind(chip, ActionButtonViewModel.Companion.withNextId(
new ActionButtonAppearance(action.getIcon().loadDrawable(mContext),
- action.getTitle(), action.getTitle()), new Function0<>() {
+ action.getTitle(), action.getTitle(), false), new Function0<>() {
@Override
public Unit invoke() {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index f437032..6f20a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal
import android.provider.Settings
-import android.service.dreams.Flags.dreamTracksFocus
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -43,6 +42,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
@@ -74,6 +74,10 @@
) : CoreStartable {
private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+ private var timeoutJob: Job? = null
+
+ private var isDreaming: Boolean = false
+
override fun start() {
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -113,47 +117,67 @@
}
.launchIn(bgScope)
- // Handle timing out back to the dream.
+ // The hub mode timeout should start as soon as the user enters hub mode. At the end of the
+ // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
+ // dream is not running, nothing will happen. However if the dream starts again underneath
+ // hub mode after the initial timeout expires, such as if the device is docked or the dream
+ // app is updated by the Play store, a new timeout should be started.
bgScope.launch {
combine(
communalInteractor.desiredScene,
// Emit a value on start so the combine starts.
communalInteractor.userActivity.emitOnStart()
) { scene, _ ->
- // Time out should run whenever we're dreaming and the hub is open, even if not
- // docked.
+ // Only timeout if we're on the hub is open.
scene == CommunalScenes.Communal
}
- // mapLatest cancels the previous action block when new values arrive, so any
- // already running timeout gets cancelled when conditions change or user interaction
- // is detected.
- .mapLatest { shouldTimeout ->
- if (!shouldTimeout) {
- return@mapLatest false
+ .collectLatest { shouldTimeout ->
+ cancelHubTimeout()
+ if (shouldTimeout) {
+ startHubTimeout()
}
-
- delay(screenTimeout.milliseconds)
- true
}
- .sample(keyguardInteractor.isDreaming, ::Pair)
- .collect { (shouldTimeout, isDreaming) ->
- if (isDreaming && shouldTimeout) {
+ }
+ bgScope.launch {
+ keyguardInteractor.isDreaming
+ .sample(communalInteractor.desiredScene, ::Pair)
+ .collectLatest { (isDreaming, scene) ->
+ this@CommunalSceneStartable.isDreaming = isDreaming
+ if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) {
+ // If dreaming starts after timeout has expired, ex. if dream restarts under
+ // the hub, just close the hub immediately.
communalInteractor.changeScene(CommunalScenes.Blank)
}
}
}
- if (dreamTracksFocus()) {
- bgScope.launch {
- communalInteractor.isIdleOnCommunal.collectLatest {
- withContext(mainDispatcher) {
- notificationShadeWindowController.setGlanceableHubShowing(it)
- }
+ bgScope.launch {
+ communalInteractor.isIdleOnCommunal.collectLatest {
+ withContext(mainDispatcher) {
+ notificationShadeWindowController.setGlanceableHubShowing(it)
}
}
}
}
+ private fun cancelHubTimeout() {
+ timeoutJob?.cancel()
+ timeoutJob = null
+ }
+
+ private fun startHubTimeout() {
+ if (timeoutJob == null) {
+ timeoutJob =
+ bgScope.launch {
+ delay(screenTimeout.milliseconds)
+ if (isDreaming) {
+ communalInteractor.changeScene(CommunalScenes.Blank)
+ }
+ timeoutJob = null
+ }
+ }
+ }
+
private suspend fun determineSceneAfterTransition(
lastStartedTransition: TransitionStep,
): SceneKey? {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
index 03f54c8..5cd15f2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
@@ -17,15 +17,23 @@
package com.android.systemui.communal.data.model
import android.appwidget.AppWidgetProviderInfo
+import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault
/**
* The widget categories to display on communal hub (where categories is a bitfield with values that
* match those in {@link AppWidgetProviderInfo}).
*/
@JvmInline
-value class CommunalWidgetCategories(
- // The default is keyguard widgets.
- val categories: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
-) {
+value class CommunalWidgetCategories(val categories: Int = defaultCategories) {
fun contains(category: Int) = (categories and category) == category
+
+ companion object {
+ val defaultCategories: Int
+ get() {
+ return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
+ if (allowAllWidgetsOnLockscreenByDefault())
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+ else 0
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e2fed6d..e5a0e50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -53,7 +53,7 @@
updateMediaModel(data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateMediaModel()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 9debe0e..88cb64c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -18,7 +18,6 @@
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
-import android.appwidget.AppWidgetProviderInfo
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.provider.Settings
@@ -108,10 +107,9 @@
.onStart { emit(Unit) }
.map {
CommunalWidgetCategories(
- // The default is to show only keyguard widgets.
secureSettings.getIntForUser(
GLANCEABLE_HUB_CONTENT_SETTING,
- AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ CommunalWidgetCategories.defaultCategories,
user.id
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index f9de609..3e5126a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -75,7 +75,7 @@
scope = bgScope,
// Start this eagerly since the value can be accessed synchronously.
started = SharingStarted.Eagerly,
- initialValue = CommunalWidgetCategories().categories
+ initialValue = CommunalWidgetCategories.defaultCategories
)
private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
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 656e5cb..97db43b 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
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
@@ -63,6 +64,7 @@
@Application private val scope: CoroutineScope,
@Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -236,6 +238,14 @@
*/
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ /**
+ * The dream overlay has its own gesture handle as the SysUI window is not visible above the
+ * dream. This flow will be false when dreaming so that we don't show a duplicate handle when
+ * opening the hub over the dream.
+ */
+ val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
+
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 840c3a8..2559137 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.graphics.Outline
import android.graphics.Rect
@@ -50,6 +51,11 @@
enforceRoundedCorners()
}
+ override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) {
+ super.setAppWidget(appWidgetId, info)
+ setPadding(0, 0, 0, 0)
+ }
+
private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 30a56a2..813fccf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -48,6 +48,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
@@ -302,7 +303,7 @@
private fun listenForSchedulingWatchdog() {
keyguardTransitionInteractor
- .transition(to = KeyguardState.GONE)
+ .transition(Edge.create(to = KeyguardState.GONE))
.filter { it.transitionState == TransitionState.FINISHED }
.onEach {
// We deliberately want to run this in background because scheduleWatchdog does
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 6c6683a..669cd94 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -38,6 +38,7 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -126,9 +127,9 @@
.launchIn(applicationScope)
merge(
- keyguardTransitionInteractor.transition(AOD, LOCKSCREEN),
- keyguardTransitionInteractor.transition(OFF, LOCKSCREEN),
- keyguardTransitionInteractor.transition(DOZING, LOCKSCREEN),
+ keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)),
+ keyguardTransitionInteractor.transition(Edge.create(OFF, LOCKSCREEN)),
+ keyguardTransitionInteractor.transition(Edge.create(DOZING, LOCKSCREEN)),
)
.filter { it.transitionState == TransitionState.STARTED }
.sample(powerInteractor.detailedWakefulness)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 6e04339..1e725eb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -16,15 +16,21 @@
package com.android.systemui.dreams;
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
+
import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
+import static com.android.systemui.Flags.communalHub;
+import static com.android.systemui.Flags.glanceableHubGestureHandle;
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.animation.Animator;
+import android.app.DreamManager;
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Handler;
@@ -37,7 +43,9 @@
import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.complication.ComplicationHostViewController;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
@@ -45,10 +53,12 @@
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.util.ViewController;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.FlowKt;
import java.util.Arrays;
@@ -68,6 +78,8 @@
private final DreamOverlayStateController mStateController;
private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final ShadeInteractor mShadeInteractor;
+ private final CommunalInteractor mCommunalInteractor;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -87,9 +99,10 @@
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
- private final CoroutineDispatcher mMainDispatcher;
+ private final CoroutineDispatcher mBackgroundDispatcher;
private final int mDreamOverlayMaxTranslationY;
private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+ private final DreamManager mDreamManager;
private long mJitterStartTimeMillis;
@@ -174,11 +187,12 @@
DreamOverlayContainerView containerView,
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
+ @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
DreamOverlayStatusBarViewController statusBarViewController,
LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@Main Handler handler,
- @Main CoroutineDispatcher mainDispatcher,
+ @Background CoroutineDispatcher backgroundDispatcher,
@Main Resources resources,
@Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
@@ -188,22 +202,33 @@
DreamOverlayAnimationsController animationsController,
DreamOverlayStateController stateController,
BouncerlessScrimController bouncerlessScrimController,
- KeyguardTransitionInteractor keyguardTransitionInteractor) {
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ ShadeInteractor shadeInteractor,
+ CommunalInteractor communalInteractor,
+ DreamManager dreamManager) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
+ mCommunalInteractor = communalInteractor;
mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mShadeInteractor = shadeInteractor;
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
R.dimen.dream_overlay_y_offset);
+
+ if (communalHub() && glanceableHubGestureHandle()) {
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ hubGestureIndicatorView.setVisibility(View.VISIBLE);
+ }
+
final View view = mComplicationHostViewController.getView();
mDreamOverlayContentView.addView(view,
@@ -211,11 +236,12 @@
ViewGroup.LayoutParams.MATCH_PARENT));
mHandler = handler;
- mMainDispatcher = mainDispatcher;
+ mBackgroundDispatcher = backgroundDispatcher;
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
+ mDreamManager = dreamManager;
}
@Override
@@ -238,11 +264,21 @@
mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
emptyRegion.recycle();
- collectFlow(
- mView,
- mKeyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
- isFinished -> mAnyBouncerShowing = isFinished,
- mMainDispatcher);
+ if (dreamHandlesBeingObscured()) {
+ collectFlow(
+ mView,
+ FlowKt.distinctUntilChanged(combineFlows(
+ mKeyguardTransitionInteractor.isFinishedInStateWhere(
+ KeyguardState::isBouncerState),
+ mShadeInteractor.isAnyExpanded(),
+ mCommunalInteractor.isCommunalShowing(),
+ (anyBouncerShowing, shadeExpanded, communalShowing) -> {
+ mAnyBouncerShowing = anyBouncerShowing;
+ return anyBouncerShowing || shadeExpanded || communalShowing;
+ })),
+ mDreamManager::setDreamIsObscured,
+ mBackgroundDispatcher);
+ }
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 999e681..789b7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import androidx.lifecycle.Lifecycle;
@@ -39,6 +40,7 @@
@Module
public abstract class DreamOverlayModule {
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
+ public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view";
public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
@@ -71,6 +73,18 @@
"R.id.dream_overlay_content must not be null");
}
+ /**
+ * Gesture indicator bar on the right edge of the screen to indicate to users that they can
+ * swipe to see their widgets on lock screen.
+ */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ @Named(HUB_GESTURE_INDICATOR_VIEW)
+ public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) {
+ return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle),
+ "R.id.glanceable_hub_handle must not be null");
+ }
+
/** */
@Provides
public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index fff0c58..1c047dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -98,7 +98,7 @@
// Notification shade window has its own logic to be visible if the hub is open, no need to
// do anything here other than send touch events over.
session.registerInputListener(ev -> {
- surfaces.handleExternalShadeWindowTouch((MotionEvent) ev);
+ surfaces.handleDreamTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
var unused = session.pop();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 221f790..c5b3c53 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
@@ -97,7 +98,7 @@
.distinctUntilChanged()
val transitionEnded =
- keyguardTransitionInteractor.transition(from = DREAMING).filter { step ->
+ keyguardTransitionInteractor.transition(Edge.create(from = DREAMING)).filter { step ->
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 9876fe4..f04cbb8 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -477,7 +477,7 @@
}
private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
- Trace.beginSection(entry.name)
+ Trace.beginSection(entry.name.take(Trace.MAX_SECTION_NAME_LEN))
preamble(entry)
val dumpTime = measureTimeMillis(block)
footer(entry, dumpTime)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 67c5564..1404340 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -29,11 +29,13 @@
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
/** A class in which engineers can define flag dependencies */
@@ -49,6 +51,7 @@
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
+ PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt
index d3f7e24..44f1c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt
@@ -17,19 +17,43 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import com.android.systemui.model.SysUiState
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.QuickStepContract
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
@SysUISingleton
class ShortcutHelperInteractor
@Inject
-constructor(private val repository: ShortcutHelperRepository) {
+constructor(
+ private val displayTracker: DisplayTracker,
+ @Background private val backgroundScope: CoroutineScope,
+ private val sysUiState: SysUiState,
+ private val repository: ShortcutHelperRepository
+) {
val state: Flow<ShortcutHelperState> = repository.state
- fun onUserLeave() {
+ fun onViewClosed() {
repository.hide()
+ setSysUiStateFlagEnabled(false)
+ }
+
+ fun onViewOpened() {
+ setSysUiStateFlagEnabled(true)
+ }
+
+ private fun setSysUiStateFlagEnabled(enabled: Boolean) {
+ backgroundScope.launch {
+ sysUiState
+ .setFlag(QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING, enabled)
+ .commitUpdate(displayTracker.defaultDisplayId)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index 934f9ee..ef4156d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -63,12 +63,13 @@
setUpSheetDismissListener()
setUpDismissOnTouchOutside()
observeFinishRequired()
+ viewModel.onViewOpened()
}
override fun onDestroy() {
super.onDestroy()
if (isFinishing) {
- viewModel.onUserLeave()
+ viewModel.onViewClosed()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 7e48c65..c623f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -38,7 +38,11 @@
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
- fun onUserLeave() {
- interactor.onUserLeave()
+ fun onViewClosed() {
+ interactor.onViewClosed()
+ }
+
+ fun onViewOpened() {
+ interactor.onViewOpened()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index dbaa297..68a252b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -37,6 +37,7 @@
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
import android.text.style.StyleSpan;
+import android.util.Log;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
@@ -212,21 +213,27 @@
@AnyThread
@Override
public Slice onBindSlice(Uri sliceUri) {
- Trace.beginSection("KeyguardSliceProvider#onBindSlice");
- Slice slice;
- synchronized (this) {
- ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
- if (needsMediaLocked()) {
- addMediaLocked(builder);
- } else {
- builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+ Slice slice = null;
+ try {
+ Trace.beginSection("KeyguardSliceProvider#onBindSlice");
+ synchronized (this) {
+ ListBuilder builder = new ListBuilder(getContext(), mSliceUri,
+ ListBuilder.INFINITY);
+ if (needsMediaLocked()) {
+ addMediaLocked(builder);
+ } else {
+ builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+ }
+ addNextAlarmLocked(builder);
+ addZenModeLocked(builder);
+ addPrimaryActionLocked(builder);
+ slice = builder.build();
}
- addNextAlarmLocked(builder);
- addZenModeLocked(builder);
- addPrimaryActionLocked(builder);
- slice = builder.build();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not initialize slice", e);
+ } finally {
+ Trace.endSection();
}
- Trace.endSection();
return slice;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2cda728..81c2d92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1076,6 +1076,33 @@
}
};
+ /**
+ * For now, the keyguard-appearing animation is a no-op, because we assume that this is
+ * happening while the screen is already off or turning off.
+ *
+ * TODO(b/278086361): create an animation for keyguard appearing over a non-showWhenLocked
+ * activity.
+ */
+ private final IRemoteAnimationRunner.Stub mAppearAnimationRunner =
+ new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to finish transition", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ }
+ };
+
private final IRemoteAnimationRunner mOccludeAnimationRunner =
new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);
@@ -1164,7 +1191,7 @@
finishedCallback.onAnimationFinished();
mOccludeByDreamAnimator = null;
} catch (RemoteException e) {
- e.printStackTrace();
+ Log.e(TAG, "Failed to finish transition", e);
}
}
});
@@ -1279,7 +1306,7 @@
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
} catch (RemoteException e) {
- e.printStackTrace();
+ Log.e(TAG, "Failed to finish transition", e);
}
}
});
@@ -1545,6 +1572,7 @@
mKeyguardTransitions.register(
KeyguardService.wrap(this, getExitAnimationRunner()),
+ KeyguardService.wrap(this, getAppearAnimationRunner()),
KeyguardService.wrap(this, getOccludeAnimationRunner()),
KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
KeyguardService.wrap(this, getUnoccludeAnimationRunner()));
@@ -2123,6 +2151,10 @@
return validatingRemoteAnimationRunner(mExitAnimationRunner);
}
+ public IRemoteAnimationRunner getAppearAnimationRunner() {
+ return validatingRemoteAnimationRunner(mAppearAnimationRunner);
+ }
+
public IRemoteAnimationRunner getOccludeAnimationRunner() {
if (KeyguardWmStateRefactor.isEnabled()) {
return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
@@ -3356,7 +3388,7 @@
}
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;
- e.printStackTrace();
+ Log.e(TAG, "Failed to report keyguardGoingAway", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index a65a882..3cbcb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -29,15 +29,20 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.utils.GlobalWindowManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -59,6 +64,7 @@
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
private val featureFlags: FeatureFlags,
+ private val sceneInteractor: SceneInteractor,
) : CoreStartable, WakefulnessLifecycle.Observer {
override fun start() {
@@ -84,9 +90,15 @@
applicationScope.launch(bgDispatcher) {
// We drop 1 to avoid triggering on initial collect().
- keyguardTransitionInteractor.transition(to = GONE).collect { transition ->
- if (transition.transitionState == TransitionState.FINISHED) {
- onKeyguardGone()
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState
+ .filter { it.isIdle(Scenes.Gone) }
+ .collect { onKeyguardGone() }
+ } else {
+ keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect {
+ if (it.transitionState == TransitionState.FINISHED) {
+ onKeyguardGone()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 00f5002..1b342ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -23,6 +23,7 @@
import android.view.WindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
@@ -40,6 +41,7 @@
private val activityTaskManagerService: IActivityTaskManager,
private val keyguardStateController: KeyguardStateController,
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/**
@@ -141,6 +143,14 @@
finishedCallback: IRemoteAnimationFinishedCallback
) {
if (apps.isNotEmpty()) {
+ // Ensure that we've started a dismiss keyguard transition. WindowManager can start the
+ // going away animation on its own, if an activity launches and then requests dismissing
+ // the keyguard. In this case, this is the first and only signal we'll receive to start
+ // a transition to GONE.
+ keyguardTransitionInteractor.startDismissKeyguardTransition(
+ reason = "Going away remote animation started"
+ )
+
goingAwayRemoteAnimationFinishedCallback = finishedCallback
keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 7f3274c..7655d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -247,7 +247,7 @@
state: TransitionState
) {
if (updateTransitionId != transitionId) {
- Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
new file mode 100644
index 0000000..80bdc65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class LockscreenSceneTransitionRepository @Inject constructor() {
+
+ /**
+ * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
+ * next transition into the Lockscreen scene is started. It will be consumed exactly once and
+ * after that the state will be set back to [DEFAULT_STATE].
+ */
+ val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE)
+
+ companion object {
+ val DEFAULT_STATE = KeyguardState.LOCKSCREEN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index dad2d96..f1e98f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -263,7 +263,9 @@
}
fun dismissKeyguard() {
- scope.launch("$TAG#dismissKeyguard") { startTransitionTo(KeyguardState.GONE) }
+ scope.launch("$TAG#dismissKeyguard") {
+ startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()")
+ }
}
private fun listenForLockscreenToGone() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 857096e..b1ef76e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
+import android.util.Log
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
@@ -78,7 +80,15 @@
private val refreshEvents: Flow<Unit> =
merge(
configurationInteractor.onAnyConfigurationChange,
- fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map { Unit },
+ fingerprintPropertyInteractor.propertiesInitialized
+ .filter { it }
+ .map { Unit }
+ .onEach {
+ Log.d(
+ "KeyguardBlueprintInteractor",
+ "triggering refreshEvent from fpPropertiesInitialized"
+ )
+ },
)
init {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 08d29d4..1aac1c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -25,6 +25,9 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -48,6 +51,7 @@
transitionInteractor: KeyguardTransitionInteractor,
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
+ sceneInteractor: SceneInteractor,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -72,7 +76,12 @@
)
private val finishedTransitionToGone: Flow<Unit> =
- transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {}
+ } else {
+ transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {}
+ }
+
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
finishedTransitionToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 2d7b737..c44a40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -55,6 +55,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -97,6 +98,7 @@
/** Bounds of the notification container. */
val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
+ SceneContainerFlag.assertInLegacyMode()
combine(
_notificationPlaceholderBounds,
sharedNotificationContainerInteractor.get().configurationBasedDimensions,
@@ -115,6 +117,7 @@
}
fun setNotificationContainerBounds(position: NotificationContainerBounds) {
+ SceneContainerFlag.assertInLegacyMode()
_notificationPlaceholderBounds.value = position
}
@@ -179,7 +182,11 @@
}
.sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
.debounce(50L)
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/** Whether the keyguard is showing or not. */
@Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
@@ -223,7 +230,19 @@
@JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
/** Whether the alternate bouncer is showing or not. */
- val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+ val alternateBouncerShowing: Flow<Boolean> =
+ bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) {
+ alternateBouncerVisible,
+ isAbleToDream ->
+ if (isAbleToDream) {
+ // If the alternate bouncer will show over a dream, it is likely that the dream has
+ // requested a dismissal, which will stop the dream. By delaying this slightly, the
+ // DREAMING->LOCKSCREEN transition will now happen first, followed by
+ // LOCKSCREEN->ALTERNATE_BOUNCER.
+ delay(600L)
+ }
+ alternateBouncerVisible
+ }
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
@@ -299,10 +318,12 @@
shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
) { legacyShadeExpansion, goneValue ->
- if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
+ val isLegacyShadeInResetPosition =
+ legacyShadeExpansion == 0f || legacyShadeExpansion == 1f
+ if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) {
// Reset the translation value
emit(0f)
- } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+ } else if (!isLegacyShadeInResetPosition) {
// On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
// transition is running, which means this is a swipe to dismiss. Values of
// 0f and 1f need to be ignored in the legacy shade expansion. These can
@@ -320,7 +341,11 @@
}
}
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 0f,
+ )
val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e711edc..75c4d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,6 +19,7 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -26,6 +27,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
@@ -41,6 +43,7 @@
private val logger: KeyguardLogger,
private val powerInteractor: PowerInteractor,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
private val shadeInteractor: ShadeInteractor,
private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
@@ -72,8 +75,8 @@
if (!SceneContainerFlag.isEnabled) {
scope.launch {
- sharedNotificationContainerViewModel.bounds.collect {
- logger.log(TAG, VERBOSE, "Notif: bounds", it)
+ sharedNotificationContainerViewModel.bounds.debounce(20L).collect {
+ logger.log(TAG, VERBOSE, "Notif: bounds (debounced)", it)
}
}
}
@@ -113,6 +116,18 @@
}
scope.launch {
+ keyguardInteractor.keyguardTranslationY.collect {
+ logger.log(TAG, VERBOSE, "keyguardTranslationY", it)
+ }
+ }
+
+ scope.launch {
+ keyguardRootViewModel.burnInModel.debounce(20L).collect {
+ logger.log(TAG, VERBOSE, "BurnInModel (debounced)", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.isKeyguardDismissible.collect {
logger.log(TAG, VERBOSE, "isDismissible", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 2c05d49..c65dc30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,7 +17,10 @@
package com.android.systemui.keyguard.domain.interactor
+import android.annotation.FloatRange
+import android.annotation.SuppressLint
import android.util.Log
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -29,11 +32,15 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,14 +75,17 @@
private val fromAlternateBouncerTransitionInteractor:
dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
+ private val sceneInteractor: dagger.Lazy<SceneInteractor>,
) {
- private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
+ private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
/**
* Numerous flows are derived from, or care directly about, the transition value in and out of a
* single state. This prevent the redundant filters from running.
*/
private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+ @SuppressLint("SharedFlowCreation")
private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
return transitionValueCache.getOrPut(state) {
MutableSharedFlow<Float>(
@@ -90,6 +100,9 @@
@Deprecated("Not performant - Use something else in this class")
val transitions = repository.transitions
+ val transitionState: StateFlow<TransitionStep> =
+ transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -99,6 +112,7 @@
* FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming
* from when we were canceled.
*/
+ @SuppressLint("SharedFlowCreation")
val startedStepWithPrecedingStep =
repository.transitions
.pairwise()
@@ -119,11 +133,11 @@
scope.launch {
repository.transitions.collect {
// FROM->TO
- transitionMap[Edge(it.from, it.to)]?.emit(it)
+ transitionMap[Edge.create(it.from, it.to)]?.emit(it)
// FROM->(ANY)
- transitionMap[Edge(it.from, null)]?.emit(it)
+ transitionMap[Edge.create(it.from, null)]?.emit(it)
// (ANY)->TO
- transitionMap[Edge(null, it.to)]?.emit(it)
+ transitionMap[Edge.create(null, it.to)]?.emit(it)
}
}
@@ -143,25 +157,70 @@
}
}
- /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */
- fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
- return transitionMap.getOrPut(edge) {
- MutableSharedFlow<TransitionStep>(
- extraBufferCapacity = 10,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
+ fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> {
+ return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer)
+ }
+
+ /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */
+ @SuppressLint("SharedFlowCreation")
+ fun transition(edge: Edge): Flow<TransitionStep> {
+ edge.verifyValidKeyguardStates()
+ val mappedEdge = getMappedEdge(edge)
+
+ val flow: Flow<TransitionStep> =
+ transitionMap.getOrPut(mappedEdge) {
+ MutableSharedFlow(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ }
+
+ return if (SceneContainerFlag.isEnabled) {
+ flow.filter {
+ val fromScene =
+ when (edge) {
+ is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
+ is Edge.StateToScene -> edge.from.mapToSceneContainerScene()
+ is Edge.SceneToState -> edge.from
+ }
+
+ val toScene =
+ when (edge) {
+ is Edge.StateToState -> edge.to?.mapToSceneContainerScene()
+ is Edge.StateToScene -> edge.to
+ is Edge.SceneToState -> edge.to.mapToSceneContainerScene()
+ }
+
+ fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
+
+ return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
+ sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene)
+ }
+ } else {
+ flow
}
}
/**
- * Receive all [TransitionStep] matching a filter of [from]->[to]. Allow nulls in order to match
- * any transition, for instance (any)->GONE.
+ * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled.
+ *
+ * Does nothing otherwise.
+ *
+ * This method should eventually be removed when new code is only written for scene container.
+ * Even when all edges are ported today, there is still development on going in production that
+ * might utilize old states.
*/
- fun transition(from: KeyguardState? = null, to: KeyguardState? = null): Flow<TransitionStep> {
- if (from == null && to == null) {
- throw IllegalArgumentException("from and to cannot both be null")
+ private fun getMappedEdge(edge: Edge): Edge.StateToState {
+ if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState
+ return when (edge) {
+ is Edge.StateToState ->
+ Edge.create(
+ from = edge.from?.mapToSceneContainerState(),
+ to = edge.to?.mapToSceneContainerState()
+ )
+ is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to)
+ is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED)
}
- return getOrCreateFlow(Edge(from = from, to = to))
}
/**
@@ -180,6 +239,7 @@
* AOD<->* transition information, mapped to dozeAmount range of AOD (1f) <->
* * (0f).
*/
+ @SuppressLint("SharedFlowCreation")
val dozeAmountTransition: Flow<TransitionStep> =
repository.transitions
.filter { step -> step.from == AOD || step.to == AOD }
@@ -201,11 +261,22 @@
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
/** The destination state of the last [TransitionState.STARTED] transition. */
+ @SuppressLint("SharedFlowCreation")
val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ val currentTransitionInfo: StateFlow<TransitionInfo> = repository.currentTransitionInfoInternal
+
+ /** The from state of the last [TransitionState.STARTED] transition. */
+ // TODO: is it performant to have several SharedFlows side by side instead of one?
+ @SuppressLint("SharedFlowCreation")
+ val startedKeyguardFromState: SharedFlow<KeyguardState> =
+ startedKeyguardTransitionStep
+ .map { step -> step.from }
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
/** Which keyguard state to use when the device goes to sleep. */
val asleepKeyguardState: StateFlow<KeyguardState> =
keyguardRepository.isAodAvailable
@@ -243,6 +314,7 @@
* sufficient. However, if you're having issues with state *during* transitions started after
* one or more canceled transitions, you probably need to use [currentKeyguardState].
*/
+ @SuppressLint("SharedFlowCreation")
val finishedKeyguardState: SharedFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
@@ -344,32 +416,37 @@
val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
- return getOrCreateFlow(Edge(from = fromState, to = null))
+ return transition(Edge.create(from = fromState, to = null))
}
fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
- return getOrCreateFlow(Edge(from = null, to = toState))
+ return transition(Edge.create(from = null, to = toState))
}
/**
* Called to start a transition that will ultimately dismiss the keyguard from the current
* state.
+ *
+ * This is called exclusively by sources that can authoritatively say we should be unlocked,
+ * including KeyguardSecurityContainerController and WindowManager.
*/
- fun startDismissKeyguardTransition() {
+ fun startDismissKeyguardTransition(reason: String = "") {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
- when (val startedState = startedKeyguardState.replayCache.last()) {
+ Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
+ when (val startedState = currentTransitionInfoInternal.value.to) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
ALTERNATE_BOUNCER ->
fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
- else ->
- Log.e(
- "KeyguardTransitionInteractor",
- "We don't know how to dismiss keyguard from state $startedState."
+ KeyguardState.GONE ->
+ Log.i(
+ TAG,
+ "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
)
+ else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
}
}
@@ -377,7 +454,7 @@
fun isInTransitionToState(
state: KeyguardState,
): Flow<Boolean> {
- return getOrCreateFlow(Edge(from = null, to = state))
+ return transition(Edge.create(from = null, to = state))
.mapLatest { it.transitionState.isTransitioning() }
.onStart { emit(false) }
.distinctUntilChanged()
@@ -386,12 +463,16 @@
/**
* Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
* completed it.
+ *
+ * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If
+ * the edges are equal before and after the flag it is sufficient to provide just [edge].
*/
- fun isInTransition(
- from: KeyguardState,
- to: KeyguardState,
- ): Flow<Boolean> {
- return getOrCreateFlow(Edge(from = from, to = to))
+ fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> {
+ return if (SceneContainerFlag.isEnabled) {
+ transition(edge)
+ } else {
+ transition(edgeWithoutSceneContainer ?: edge)
+ }
.mapLatest { it.transitionState.isTransitioning() }
.onStart { emit(false) }
.distinctUntilChanged()
@@ -403,7 +484,7 @@
fun isInTransitionFromState(
state: KeyguardState,
): Flow<Boolean> {
- return getOrCreateFlow(Edge(from = state, to = null))
+ return transition(Edge.create(from = state, to = null))
.mapLatest { it.transitionState.isTransitioning() }
.onStart { emit(false) }
.distinctUntilChanged()
@@ -454,7 +535,7 @@
* If you only care about a single state for both from and to, instead use the optimized
* [isInTransition].
*/
- fun isInTransitionWhere(
+ private fun isInTransitionWhere(
fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
): Flow<Boolean> {
return repository.transitions
@@ -491,7 +572,23 @@
return startedKeyguardState.replayCache.last()
}
+ fun getStartedFromState(): KeyguardState {
+ return startedKeyguardFromState.replayCache.last()
+ }
+
fun getFinishedState(): KeyguardState {
return finishedKeyguardState.replayCache.last()
}
+
+ suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
+
+ fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) = repository.updateTransition(transitionId, value, state)
+
+ companion object {
+ private val TAG = KeyguardTransitionInteractor::class.simpleName
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 2d944c6..9443570 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -103,6 +103,7 @@
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> true
KeyguardState.OCCLUDED -> true
+ KeyguardState.UNDEFINED -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index d95c38e..3c66186 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.CoreStartable
+import com.android.systemui.keyguard.domain.interactor.scenetransition.LockscreenSceneTransitionInteractor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -31,6 +32,13 @@
abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
@Binds
+ @IntoMap
+ @ClassKey(LockscreenSceneTransitionInteractor::class)
+ abstract fun bindLockscreenSceneTransitionInteractor(
+ impl: LockscreenSceneTransitionInteractor
+ ): CoreStartable
+
+ @Binds
@IntoSet
abstract fun fromPrimaryBouncer(
impl: FromPrimaryBouncerTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index dc35e43..1e2db7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.scene.domain.interactor.SceneInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
new file mode 100644
index 0000000..3baeb76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.scenetransition
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository.Companion.DEFAULT_STATE
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * This class listens to scene framework scene transitions and manages keyguard transition framework
+ * (KTF) states accordingly.
+ *
+ * There are a few rules:
+ * - When scene framework is on a scene outside of Lockscreen, then KTF is in state UNDEFINED
+ * - When scene framework is on Lockscreen, KTF is allowed to change its scenes freely
+ * - When scene framework is transitioning away from Lockscreen, then KTF transitions to UNDEFINED
+ * and shares its progress.
+ * - When scene framework is transitioning to Lockscreen, then KTF starts a transition to LOCKSCREEN
+ * but it is allowed to interrupt this transition and transition to other internal KTF states
+ *
+ * There are a few notable differences between SceneTransitionLayout (STL) and KTF that require
+ * special treatment when synchronizing both state machines.
+ * - STL does not emit cancelations as KTF does
+ * - Both STL and KTF require state continuity, though the rules from where starting the next
+ * transition is allowed is different on each side:
+ * - STL has a concept of "currentScene" which can be chosen to be either A or B in a A -> B
+ * transition. The currentScene determines which transition can be started next. In KTF the
+ * currentScene is always the `to` state. Which means transitions can only be started from B.
+ * This also holds true when A -> B was canceled: the next transition needs to start from B.
+ * - KTF can not settle back in its from scene, instead it needs to cancel and start a reversed
+ * transition.
+ */
+@SysUISingleton
+class LockscreenSceneTransitionInteractor
+@Inject
+constructor(
+ val transitionInteractor: KeyguardTransitionInteractor,
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: SceneInteractor,
+ private val repository: LockscreenSceneTransitionRepository,
+) : CoreStartable, SceneInteractor.OnSceneAboutToChangeListener {
+
+ private var currentTransitionId: UUID? = null
+ private var progressJob: Job? = null
+
+ override fun start() {
+ sceneInteractor.registerSceneStateProcessor(this)
+ listenForSceneTransitionProgress()
+ }
+
+ override fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) {
+ if (toScene != Scenes.Lockscreen || sceneState == null) return
+ if (sceneState !is KeyguardState) {
+ throw IllegalArgumentException("Lockscreen sceneState needs to be a KeyguardState.")
+ }
+ repository.nextLockscreenTargetState.value = sceneState
+ }
+
+ private fun listenForSceneTransitionProgress() {
+ applicationScope.launch {
+ sceneInteractor.transitionState
+ .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen))
+ .collect { (prevTransition, transition) ->
+ when (transition) {
+ is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition)
+ is ObservableTransitionState.Transition -> handleTransition(transition)
+ }
+ }
+ }
+ }
+
+ private suspend fun handleIdle(
+ prevTransition: ObservableTransitionState,
+ idle: ObservableTransitionState.Idle
+ ) {
+ if (currentTransitionId == null) return
+ if (prevTransition !is ObservableTransitionState.Transition) return
+
+ if (idle.currentScene == prevTransition.toScene) {
+ finishCurrentTransition()
+ } else {
+ val targetState =
+ if (idle.currentScene == Scenes.Lockscreen) {
+ transitionInteractor.getStartedFromState()
+ } else {
+ UNDEFINED
+ }
+ finishReversedTransitionTo(targetState)
+ }
+ }
+
+ private fun finishCurrentTransition() {
+ transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+ resetTransitionData()
+ }
+
+ private suspend fun finishReversedTransitionTo(state: KeyguardState) {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = transitionInteractor.currentTransitionInfo.value.to,
+ to = state,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE
+ )
+ currentTransitionId = transitionInteractor.startTransition(newTransition)
+ transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+ resetTransitionData()
+ }
+
+ private fun resetTransitionData() {
+ progressJob?.cancel()
+ progressJob = null
+ currentTransitionId = null
+ }
+
+ private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
+ if (transition.fromScene == Scenes.Lockscreen) {
+ if (currentTransitionId != null) {
+ val currentToState = transitionInteractor.currentTransitionInfo.value.to
+ if (currentToState == UNDEFINED) {
+ transitionKtfTo(transitionInteractor.getStartedFromState())
+ }
+ }
+ startTransitionFromLockscreen()
+ collectProgress(transition)
+ } else if (transition.toScene == Scenes.Lockscreen) {
+ if (currentTransitionId != null) {
+ transitionKtfTo(UNDEFINED)
+ }
+ startTransitionToLockscreen()
+ collectProgress(transition)
+ } else {
+ transitionKtfTo(UNDEFINED)
+ }
+ }
+
+ private suspend fun transitionKtfTo(state: KeyguardState) {
+ // TODO(b/330311871): This is based on a sharedFlow and thus might not be up-to-date and
+ // cause a race condition. (There is no known scenario that is currently affected.)
+ val currentTransition = transitionInteractor.transitionState.value
+ if (currentTransition.isFinishedIn(state)) {
+ // This is already the state we want to be in
+ resetTransitionData()
+ } else if (currentTransition.isTransitioning(to = state)) {
+ finishCurrentTransition()
+ } else {
+ finishReversedTransitionTo(state)
+ }
+ }
+
+ private fun collectProgress(transition: ObservableTransitionState.Transition) {
+ progressJob?.cancel()
+ progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } }
+ }
+
+ private suspend fun startTransitionToLockscreen() {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = UNDEFINED,
+ to = repository.nextLockscreenTargetState.value,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ repository.nextLockscreenTargetState.value = DEFAULT_STATE
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransitionFromLockscreen() {
+ val currentState = transitionInteractor.currentTransitionInfo.value.to
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = currentState,
+ to = UNDEFINED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransition(transitionInfo: TransitionInfo) {
+ if (currentTransitionId != null) {
+ resetTransitionData()
+ }
+ currentTransitionId = transitionInteractor.startTransition(transitionInfo)
+ }
+
+ private fun updateProgress(progress: Float) {
+ if (currentTransitionId == null) return
+ transitionInteractor.updateTransition(
+ currentTransitionId!!,
+ progress.coerceIn(0f, 1f),
+ RUNNING
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
index a0f9be6..4f516f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
@@ -15,8 +15,98 @@
*/
package com.android.systemui.keyguard.shared.model
-/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */
-data class Edge(
- val from: KeyguardState?,
- val to: KeyguardState?,
-)
+import android.util.Log
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+
+/**
+ * Represents an edge either between two Keyguard Transition Framework states (KTF) or a KTF state
+ * and a scene container scene. Passing [null] in either [from] or [to] indicates a wildcard.
+ *
+ * Wildcards are not allowed for transitions involving a scene. Use [sceneInteractor] directly
+ * instead. Reason: [TransitionStep]s are not emitted for every edge leading into/out of a scene.
+ * For example: Lockscreen -> Gone would be emitted as LOCKSCREEN -> UNDEFINED but Bouncer -> Gone
+ * would not emit anything.
+ */
+sealed class Edge {
+
+ fun verifyValidKeyguardStates() {
+ when (this) {
+ is StateToState -> verifyValidKeyguardStates(from, to)
+ is SceneToState -> verifyValidKeyguardStates(null, to)
+ is StateToScene -> verifyValidKeyguardStates(from, null)
+ }
+ }
+
+ private fun verifyValidKeyguardStates(from: KeyguardState?, to: KeyguardState?) {
+ val mappedFrom = from?.mapToSceneContainerState()
+ val mappedTo = to?.mapToSceneContainerState()
+
+ val fromChanged = from != mappedFrom
+ val toChanged = to != mappedTo
+
+ if (SceneContainerFlag.isEnabled) {
+ if (fromChanged && toChanged) {
+ // TODO:(b/330311871) As we come close to having all current edges converted these
+ // error messages can be converted to throw such that future developers fail early
+ // when they introduce invalid edges.
+ Log.e(
+ TAG,
+ """
+ The edge ${from?.name} => ${to?.name} was automatically converted to
+ ${mappedFrom?.name} => ${mappedTo?.name} but does not exist anymore in KTF.
+ Please remove or port this edge to scene container."""
+ .trimIndent(),
+ )
+ } else if ((fromChanged && to == null) || (toChanged && from == null)) {
+ Log.e(
+ TAG,
+ """
+ The edge ${from?.name} => ${to?.name} was automatically converted to
+ ${mappedFrom?.name} => ${mappedTo?.name}. Wildcards are not allowed together
+ with UNDEFINED because it will only be tracking edges leading in and out of
+ the Lockscreen scene but miss others. Please remove or port this edge."""
+ .trimIndent(),
+ Exception()
+ )
+ } else if (fromChanged || toChanged) {
+ Log.w(
+ TAG,
+ """
+ The edge ${from?.name} => ${to?.name} was automatically converted to
+ ${mappedFrom?.name} => ${mappedTo?.name} it probably exists but needs explicit
+ conversion. Please remove or port this edge to scene container."""
+ .trimIndent(),
+ )
+ }
+ } else {
+ if (from == UNDEFINED || to == UNDEFINED) {
+ Log.e(
+ TAG,
+ "UNDEFINED should not be used when scene container is disabled",
+ )
+ }
+ }
+ }
+
+ data class StateToState(val from: KeyguardState?, val to: KeyguardState?) : Edge() {
+ init {
+ check(!(from == null && to == null)) { "to and from can't both be null" }
+ }
+ }
+
+ data class StateToScene(val from: KeyguardState, val to: SceneKey) : Edge()
+
+ data class SceneToState(val from: SceneKey, val to: KeyguardState) : Edge()
+
+ companion object {
+ private const val TAG = "Edge"
+
+ fun create(from: KeyguardState? = null, to: KeyguardState? = null) = StateToState(from, to)
+
+ fun create(from: KeyguardState, to: SceneKey) = StateToScene(from, to)
+
+ fun create(from: SceneKey, to: KeyguardState) = SceneToState(from, to)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7d05539..6a2bb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -15,9 +15,12 @@
*/
package com.android.systemui.keyguard.shared.model
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+
/** List of all possible states to transition to/from */
enum class KeyguardState {
- /*
+ /**
* The display is completely off, as well as any sensors that would trigger the device to wake
* up.
*/
@@ -29,13 +32,13 @@
* notifications is enabled, allowing the device to quickly wake up.
*/
DOZING,
- /*
+ /**
* A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
* DOZING is an example of special version of this state. Dreams may be implemented by third
* parties to present their own UI over keyguard, like a screensaver.
*/
DREAMING,
- /*
+ /**
* A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
* It is a special version of DREAMING state but not DOZING. The active dream will be windowless
* and hosted in the lockscreen.
@@ -47,17 +50,17 @@
* low-power mode without a UI, then it is DOZING.
*/
AOD,
- /*
+ /**
* The security screen prompt containing UI to prompt the user to use a biometric credential
* (ie: fingerprint). When supported, this may show before showing the primary bouncer.
*/
ALTERNATE_BOUNCER,
- /*
+ /**
* The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their
* credentials.
*/
PRIMARY_BOUNCER,
- /*
+ /**
* Device is actively displaying keyguard UI and is not in low-power mode. Device may be
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/
@@ -68,17 +71,56 @@
* or dream, as well as swipe down for the notifications and up for the bouncer.
*/
GLANCEABLE_HUB,
- /*
- * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
- * is being removed, but there are other cases where the user is swiping away keyguard, such as
+ /**
+ * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is
+ * being removed, but there are other cases where the user is swiping away keyguard, such as
* with SWIPE security method or face unlock without bypass.
*/
GONE,
- /*
- * An activity is displaying over the keyguard.
+ /**
+ * Only used in scene framework. This means we are currently on any scene framework scene that
+ * is not Lockscreen. Transitions to and from UNDEFINED are always bound to the
+ * [SceneTransitionLayout] scene transition that either transitions to or from the Lockscreen
+ * scene. These transitions are automatically handled by [LockscreenSceneTransitionInteractor].
*/
+ UNDEFINED,
+ /** An activity is displaying over the keyguard. */
OCCLUDED;
+ fun mapToSceneContainerState(): KeyguardState {
+ return when (this) {
+ OFF,
+ DOZING,
+ DREAMING,
+ DREAMING_LOCKSCREEN_HOSTED,
+ AOD,
+ ALTERNATE_BOUNCER,
+ OCCLUDED,
+ LOCKSCREEN -> this
+ GLANCEABLE_HUB,
+ PRIMARY_BOUNCER,
+ GONE,
+ UNDEFINED -> UNDEFINED
+ }
+ }
+
+ fun mapToSceneContainerScene(): SceneKey? {
+ return when (this) {
+ OFF,
+ DOZING,
+ DREAMING,
+ DREAMING_LOCKSCREEN_HOSTED,
+ AOD,
+ ALTERNATE_BOUNCER,
+ OCCLUDED,
+ LOCKSCREEN -> Scenes.Lockscreen
+ GLANCEABLE_HUB -> Scenes.Communal
+ PRIMARY_BOUNCER -> Scenes.Bouncer
+ GONE -> Scenes.Gone
+ UNDEFINED -> null
+ }
+ }
+
companion object {
/** Whether the lockscreen is visible when we're FINISHED in the given state. */
@@ -109,6 +151,7 @@
LOCKSCREEN -> true
GONE -> true
OCCLUDED -> true
+ UNDEFINED -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0fa6f4f..2b4c4af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -30,4 +30,12 @@
value: Float,
transitionState: TransitionState,
) : this(info.from, info.to, value, transitionState, info.ownerName)
+
+ fun isTransitioning(from: KeyguardState? = null, to: KeyguardState? = null): Boolean {
+ return (from == null || this.from == from) && (to == null || this.to == to)
+ }
+
+ fun isFinishedIn(state: KeyguardState): Boolean {
+ return to == state && transitionState == TransitionState.FINISHED
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 735b109..23aa21c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -52,20 +53,20 @@
/** Invoke once per transition between FROM->TO states to get access to a shared flow. */
fun setup(
duration: Duration,
- from: KeyguardState?,
- to: KeyguardState?,
+ edge: Edge,
): FlowBuilder {
- if (from == null && to == null) {
- throw IllegalArgumentException("from and to are both null")
- }
-
- return FlowBuilder(duration, Edge(from, to))
+ return FlowBuilder(duration, edge)
}
inner class FlowBuilder(
private val transitionDuration: Duration,
private val edge: Edge,
) {
+ fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder {
+ if (SceneContainerFlag.isEnabled) return this
+ return setup(this.transitionDuration, edge)
+ }
+
/**
* Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
* in the range of [0, 1]. View animations should begin and end within a subset of this
@@ -117,7 +118,7 @@
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
- if ((startTime + duration).compareTo(transitionDuration) > 0) {
+ if ((startTime + duration) > transitionDuration) {
throw IllegalArgumentException(
"startTime($startTime) + duration($duration) must be" +
" <= transitionDuration($transitionDuration)"
@@ -153,7 +154,7 @@
}
return transitionInteractor
- .getOrCreateFlow(edge)
+ .transition(edge)
.map { step ->
StateToValue(
from = step.from,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 218967c..7c745bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -57,9 +57,9 @@
addTransition(SmartspaceMoveTransition(config, clockViewModel))
}
- open class VisibilityBoundsTransition() : Transition() {
- var captureSmartspace: Boolean = false
-
+ abstract class VisibilityBoundsTransition() : Transition() {
+ abstract val captureSmartspace: Boolean
+ protected val TAG = this::class.simpleName!!
override fun captureEndValues(transition: TransitionValues) = captureValues(transition)
override fun captureStartValues(transition: TransitionValues) = captureValues(transition)
override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES
@@ -76,7 +76,7 @@
parent.findViewById<View>(sharedR.id.bc_smartspace_view)
?: parent.findViewById<View>(R.id.keyguard_slice_view)
if (targetSSView == null) {
- Log.e(TAG, "Failed to find smartspace equivalent target for animation")
+ Log.e(TAG, "Failed to find smartspace equivalent target under $parent")
return
}
transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect()
@@ -109,14 +109,12 @@
var fromIsVis = fromVis == View.VISIBLE
var fromAlpha = startValues.values[PROP_ALPHA] as Float
val fromBounds = startValues.values[PROP_BOUNDS] as Rect
- val fromSSBounds =
- if (captureSmartspace) startValues.values[SMARTSPACE_BOUNDS] as Rect else null
+ val fromSSBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect?
val toView = endValues.view
val toVis = endValues.values[PROP_VISIBILITY] as Int
val toBounds = endValues.values[PROP_BOUNDS] as Rect
- val toSSBounds =
- if (captureSmartspace) endValues.values[SMARTSPACE_BOUNDS] as Rect else null
+ val toSSBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect?
val toIsVis = toVis == View.VISIBLE
val toAlpha = if (toIsVis) 1f else 0f
@@ -221,9 +219,6 @@
private const val SMARTSPACE_BOUNDS = "ClockSizeTransition:SSBounds"
private val TRANSITION_PROPERTIES =
arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
-
- private val DEBUG = false
- private val TAG = VisibilityBoundsTransition::class.simpleName!!
}
}
@@ -232,18 +227,24 @@
val viewModel: KeyguardClockViewModel,
val smartspaceViewModel: KeyguardSmartspaceViewModel,
) : VisibilityBoundsTransition() {
+ override val captureSmartspace = !viewModel.isLargeClockVisible.value
+
init {
duration = CLOCK_IN_MILLIS
startDelay = CLOCK_IN_START_DELAY_MILLIS
interpolator = CLOCK_IN_INTERPOLATOR
- captureSmartspace =
- !viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled
if (viewModel.isLargeClockVisible.value) {
viewModel.currentClock.value?.let {
+ if (DEBUG) Log.i(TAG, "Large Clock In: ${it.largeClock.layout.views}")
it.largeClock.layout.views.forEach { addTarget(it) }
}
+ ?: run {
+ Log.e(TAG, "No large clock set, falling back")
+ addTarget(R.id.lockscreen_clock_view_large)
+ }
} else {
+ if (DEBUG) Log.i(TAG, "Small Clock In")
addTarget(R.id.lockscreen_clock_view)
}
}
@@ -282,7 +283,6 @@
val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
const val SMALL_CLOCK_IN_MOVE_SCALE =
CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat()
- private val TAG = ClockFaceInTransition::class.simpleName!!
}
}
@@ -291,18 +291,24 @@
val viewModel: KeyguardClockViewModel,
val smartspaceViewModel: KeyguardSmartspaceViewModel,
) : VisibilityBoundsTransition() {
+ override val captureSmartspace = viewModel.isLargeClockVisible.value
+
init {
duration = CLOCK_OUT_MILLIS
interpolator = CLOCK_OUT_INTERPOLATOR
- captureSmartspace =
- viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled
if (viewModel.isLargeClockVisible.value) {
+ if (DEBUG) Log.i(TAG, "Small Clock Out")
addTarget(R.id.lockscreen_clock_view)
} else {
viewModel.currentClock.value?.let {
+ if (DEBUG) Log.i(TAG, "Large Clock Out: ${it.largeClock.layout.views}")
it.largeClock.layout.views.forEach { addTarget(it) }
}
+ ?: run {
+ Log.e(TAG, "No large clock set, falling back")
+ addTarget(R.id.lockscreen_clock_view_large)
+ }
}
}
@@ -339,7 +345,6 @@
val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
const val SMALL_CLOCK_OUT_MOVE_SCALE =
CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat()
- private val TAG = ClockFaceOutTransition::class.simpleName!!
}
}
@@ -348,6 +353,8 @@
val config: IntraBlueprintTransition.Config,
viewModel: KeyguardClockViewModel,
) : VisibilityBoundsTransition() {
+ override val captureSmartspace = false
+
init {
duration =
if (viewModel.isLargeClockVisible.value) STATUS_AREA_MOVE_UP_MILLIS
@@ -367,4 +374,8 @@
const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
}
}
+
+ companion object {
+ val DEBUG = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index 4fd92d7..9da11ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -42,8 +44,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD),
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
index 9649af73..55a48b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -42,8 +44,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromAlternateBouncerTransitionInteractor.TO_DOZING_DURATION,
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.DOZING,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = DOZING),
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 8c6be98..bb4fb79 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -19,11 +19,13 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -44,11 +46,14 @@
private val statusBarStateController: SysuiStatusBarStateController,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_GONE_DURATION,
- from = ALTERNATE_BOUNCER,
- to = KeyguardState.GONE,
- )
+ animationFlow
+ .setup(
+ duration = TO_GONE_DURATION,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = GONE),
+ )
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
index 27febd3..3f2ef29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
@@ -18,8 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_OCCLUDED_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -40,8 +41,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_OCCLUDED_DURATION,
- from = ALTERNATE_BOUNCER,
- to = KeyguardState.OCCLUDED,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED),
)
override val deviceEntryParentViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 7592881..f0bccac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -37,11 +40,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- )
+ animationFlow
+ .setup(
+ duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER),
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
index 5cf100e..4128c52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
@@ -34,8 +34,8 @@
alternateBouncerInteractor: AlternateBouncerInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
- private val deviceSupportsAlternateBouncer: Flow<Boolean> =
- alternateBouncerInteractor.alternateBouncerSupported
+ val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer
+
private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> =
keyguardTransitionInteractor
.transitionValue(KeyguardState.ALTERNATE_BOUNCER)
@@ -43,8 +43,8 @@
.distinctUntilChanged()
val alternateBouncerWindowRequired: Flow<Boolean> =
- deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer ->
- if (deviceSupportsAlternateBouncer) {
+ canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer ->
+ if (canShowAlternateBouncer) {
isTransitioningToOrFromOrShowingAlternateBouncer
} else {
flowOf(false)
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 adc090d..8e8b09d 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
@@ -19,9 +19,12 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,11 +40,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromAodTransitionInteractor.TO_GONE_DURATION,
- from = KeyguardState.AOD,
- to = KeyguardState.GONE,
- )
+ animationFlow
+ .setup(
+ duration = FromAodTransitionInteractor.TO_GONE_DURATION,
+ edge = Edge.create(from = AOD, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = AOD, to = GONE),
+ )
/**
* AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index cbbb820..b267ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -19,9 +19,10 @@
import android.util.MathUtils
import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -39,7 +40,6 @@
class AodToLockscreenTransitionViewModel
@Inject
constructor(
- deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
shadeInteractor: ShadeInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -47,8 +47,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = AOD, to = LOCKSCREEN),
)
private var isShadeExpanded = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 445575f..2497def 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -19,7 +19,9 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -36,8 +38,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
- from = KeyguardState.AOD,
- to = KeyguardState.OCCLUDED,
+ edge = Edge.create(from = AOD, to = OCCLUDED),
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index 9a23007..35f05f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -37,11 +40,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- from = KeyguardState.AOD,
- to = KeyguardState.PRIMARY_BOUNCER,
- )
+ animationFlow
+ .setup(
+ duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ edge = Edge.create(from = AOD, to = Scenes.Bouncer),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER),
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
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 570f377..caa0436 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,13 @@
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.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
@@ -73,8 +75,12 @@
return animationFlow
.setup(
duration = duration,
- from = from,
- to = GONE,
+ // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> scene
+ // transition
+ edge = Edge.create(from = from, to = Scenes.Gone)
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = from, to = GONE),
)
.sharedFlow(
duration = duration,
@@ -96,11 +102,16 @@
var leaveShadeOpen: Boolean = false
var willRunDismissFromKeyguard: Boolean = false
val transitionAnimation =
- animationFlow.setup(
- duration = duration,
- from = fromState,
- to = GONE,
- )
+ animationFlow
+ .setup(
+ duration = duration,
+ // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene ->
+ // scene transition
+ edge = Edge.create(from = fromState, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = fromState, to = GONE),
+ )
return shadeInteractor.anyExpansion
.map { it > 0f }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 87324a2..6f8389f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -117,7 +117,8 @@
KeyguardState.DOZING,
KeyguardState.DREAMING,
KeyguardState.PRIMARY_BOUNCER,
- KeyguardState.AOD -> emit(0f)
+ KeyguardState.AOD,
+ KeyguardState.UNDEFINED -> emit(0f)
KeyguardState.ALTERNATE_BOUNCER,
KeyguardState.LOCKSCREEN -> emit(1f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 53b2697..ae83c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -148,10 +148,11 @@
KeyguardState.GLANCEABLE_HUB,
KeyguardState.GONE,
KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ KeyguardState.UNDEFINED, -> 0f
KeyguardState.AOD,
KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.LOCKSCREEN -> 1f
+ KeyguardState.LOCKSCREEN, -> 1f
}
}
val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
index 8851a51..77ebfce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
@@ -19,9 +19,12 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,11 +40,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_GONE_DURATION,
- from = KeyguardState.DOZING,
- to = KeyguardState.GONE,
- )
+ animationFlow
+ .setup(
+ duration = TO_GONE_DURATION,
+ edge = Edge.create(from = DOZING, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = DOZING, to = GONE),
+ )
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
index 168d6e1..a460d51 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -39,8 +41,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION,
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = DOZING, to = LOCKSCREEN),
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index c0b1195..f33752f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -19,7 +19,9 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -38,8 +40,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
- from = KeyguardState.DOZING,
- to = KeyguardState.OCCLUDED,
+ edge = Edge.create(from = DOZING, to = OCCLUDED),
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index 4395c34..7ddf641 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -38,11 +41,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_PRIMARY_BOUNCER_DURATION,
- from = KeyguardState.DOZING,
- to = KeyguardState.PRIMARY_BOUNCER,
- )
+ animationFlow
+ .setup(
+ duration = TO_PRIMARY_BOUNCER_DURATION,
+ edge = Edge.create(from = DOZING, to = Scenes.Bouncer),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER),
+ )
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
index 67568e1..57ed455 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
@@ -18,7 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -34,8 +36,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = DREAMING_LOCKSCREEN_HOSTED, to = LOCKSCREEN),
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 0fa7475..754ed6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -40,12 +42,12 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromDreamingTransitionInteractor.TO_AOD_DURATION,
- from = KeyguardState.DREAMING,
- to = KeyguardState.AOD,
+ edge = Edge.create(from = DREAMING, to = AOD),
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
+
override val deviceEntryParentViewAlpha: Flow<Float> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index a083c24e..00aa102 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,10 +19,13 @@
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -40,11 +43,14 @@
configurationInteractor: ConfigurationInteractor,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_GLANCEABLE_HUB_DURATION,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(from = DREAMING, to = Scenes.Communal),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB),
+ )
val dreamOverlayTranslationX: Flow<Float> =
configurationInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
index ec7b931..1bdf6d29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
@@ -18,10 +18,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class DreamingToGoneTransitionViewModel
@@ -31,13 +34,15 @@
) {
private val transitionAnimation =
- animationFlow.setup(
+ animationFlow
+ .setup(
duration = FromDreamingTransitionInteractor.TO_GONE_DURATION,
- from = KeyguardState.DREAMING,
- to = KeyguardState.GONE,
+ edge = Edge.create(from = DREAMING, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = DREAMING, to = GONE),
)
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index f191aa7..82381eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,7 +20,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -45,8 +47,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.DREAMING,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = DREAMING, to = LOCKSCREEN),
)
/** Dream overlay y-translation on exit */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index 3716458..d594488 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -19,10 +19,13 @@
import com.android.app.animation.Interpolators
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -41,11 +44,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FROM_GLANCEABLE_HUB_DURATION,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.DREAMING,
- )
+ animationFlow
+ .setup(
+ duration = FROM_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+ )
val dreamOverlayAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e05b500..046b95f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -20,10 +20,13 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,11 +48,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.LOCKSCREEN,
- )
+ animationFlow
+ .setup(
+ duration = TO_LOCKSCREEN_DURATION,
+ edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
+ )
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt
index 300121f..cd98bb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_OCCLUDED_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -32,11 +35,12 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_OCCLUDED_DURATION,
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED,
- )
+ animationFlow
+ .setup(
+ duration = TO_OCCLUDED_DURATION,
+ edge = Edge.create(from = Scenes.Communal, to = OCCLUDED),
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 3540bec..74f7d75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -20,10 +20,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,11 +45,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_AOD_DURATION,
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- )
+ animationFlow
+ .setup(
+ duration = TO_AOD_DURATION,
+ edge = Edge.create(from = Scenes.Gone, to = AOD),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = AOD),
+ )
/** y-translation from the top of the screen for AOD */
fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
index 80a6bda..70c0032 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
@@ -19,9 +19,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,11 +43,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_DOZING_DURATION,
- from = KeyguardState.GONE,
- to = KeyguardState.DOZING,
- )
+ animationFlow
+ .setup(
+ duration = TO_DOZING_DURATION,
+ edge = Edge.create(from = Scenes.Gone, to = DOZING),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = DOZING),
+ )
val lockscreenAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
index b527463..627f0de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -18,8 +18,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -36,11 +39,14 @@
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_DREAMING_DURATION,
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- )
+ animationFlow
+ .setup(
+ duration = TO_DREAMING_DURATION,
+ edge = Edge.create(from = Scenes.Gone, to = DREAMING_LOCKSCREEN_HOSTED),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = DREAMING_LOCKSCREEN_HOSTED),
+ )
/** Lockscreen views alpha - hide immediately */
val lockscreenAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 102242a..f8b6e28 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -19,8 +19,11 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -34,11 +37,14 @@
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_DREAMING_DURATION,
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING,
- )
+ animationFlow
+ .setup(
+ duration = TO_DREAMING_DURATION,
+ edge = Edge.create(from = Scenes.Gone, to = DREAMING),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = DREAMING),
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
index a2ce408..08ec43f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -33,11 +36,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN
- )
+ animationFlow
+ .setup(
+ duration = TO_LOCKSCREEN_DURATION,
+ edge = Edge.create(from = Scenes.Gone, to = LOCKSCREEN),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = GONE, to = LOCKSCREEN),
+ )
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index bbcea56..f405b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -38,6 +39,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
@@ -115,14 +117,18 @@
private val shadeInteractor: ShadeInteractor,
) {
private var burnInJob: Job? = null
- private val burnInModel = MutableStateFlow(BurnInModel())
+ internal val burnInModel = MutableStateFlow(BurnInModel())
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardState
.filter { it == AOD || it == LOCKSCREEN }
.map { VISIBLE }
- val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
+ val goneToAodTransition =
+ keyguardTransitionInteractor.transition(
+ edge = Edge.create(Scenes.Gone, AOD),
+ edgeWithoutSceneContainer = Edge.create(GONE, AOD)
+ )
private val goneToAodTransitionRunning: Flow<Boolean> =
goneToAodTransition
@@ -144,7 +150,10 @@
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.isInTransition(from = LOCKSCREEN, to = GONE),
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+ edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
+ ),
isOnLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 1f9f304..8b5b347 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -20,7 +20,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -45,8 +47,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
+ edge = Edge.create(from = LOCKSCREEN, to = AOD),
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index c836f01..27a1f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -40,8 +42,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_DOZING_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DOZING,
+ edge = Edge.create(from = LOCKSCREEN, to = DOZING),
)
val lockscreenAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
index 19b9cf47..778dbed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
@@ -18,7 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -34,8 +36,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_HOSTED_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ edge = Edge.create(from = LOCKSCREEN, to = DREAMING_LOCKSCREEN_HOSTED),
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index 13522a6..579abeb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -40,8 +42,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_DREAMING_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING,
+ edge = Edge.create(from = LOCKSCREEN, to = DREAMING),
)
/** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index dae7897..c7273b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -20,10 +20,13 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,11 +48,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ animationFlow
+ .setup(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
+ )
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index f03625e..1314e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -19,10 +19,13 @@
import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -42,11 +45,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation: FlowBuilder =
- animationFlow.setup(
- duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- )
+ animationFlow
+ .setup(
+ duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
+ edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = LOCKSCREEN, to = GONE),
+ )
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index dd6652e..fcf8c14f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,7 +20,9 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
@@ -45,8 +47,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_OCCLUDED_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
+ edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = OCCLUDED),
)
/** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 0cfc757..23c44b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,11 +42,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.PRIMARY_BOUNCER,
- )
+ animationFlow
+ .setup(
+ duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
+ )
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
index d7ba46b..706a3c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -41,8 +43,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromOccludedTransitionInteractor.TO_AOD_DURATION,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.AOD,
+ edge = Edge.create(from = OCCLUDED, to = AOD),
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
index 91554e3..af01930 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
@@ -18,7 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -38,8 +40,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.DOZING,
+ edge = Edge.create(from = OCCLUDED, to = DOZING),
)
/** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt
index 73a4a9d..47e202b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt
@@ -18,9 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -32,11 +35,12 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_GLANCEABLE_HUB_DURATION,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GLANCEABLE_HUB,
- )
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(OCCLUDED, Scenes.Communal)
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, GLANCEABLE_HUB))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
index d2c9cfb..98dba39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
@@ -17,8 +17,11 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,11 +36,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = DEFAULT_DURATION,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.GONE,
- )
+ animationFlow
+ .setup(
+ duration = DEFAULT_DURATION,
+ edge = Edge.create(from = OCCLUDED, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = OCCLUDED, to = GONE),
+ )
fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
var currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index a09d58a..36c7d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -23,7 +23,9 @@
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
@@ -56,8 +58,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = TO_LOCKSCREEN_DURATION,
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = OCCLUDED, to = LOCKSCREEN),
)
/** Lockscreen views y-translation */
@@ -101,7 +102,7 @@
.filter { (wasOccluded, isOccluded) ->
wasOccluded &&
!isOccluded &&
- keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+ keyguardTransitionInteractor.getCurrentState() == OCCLUDED
}
.map { 0f }
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index cf6a533..1eecbd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -17,7 +17,9 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
@@ -34,8 +36,7 @@
private val transitionAnimation =
animationFlow.setup(
duration = 250.milliseconds,
- from = KeyguardState.OFF,
- to = KeyguardState.LOCKSCREEN,
+ edge = Edge.create(from = OFF, to = LOCKSCREEN),
)
val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index 942903b..009f85d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -19,9 +19,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,11 +45,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.AOD,
- )
+ animationFlow
+ .setup(
+ duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
+ edge = Edge.create(from = Scenes.Bouncer, to = AOD),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD),
+ )
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
index 13f651a..e5bb464 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
@@ -19,9 +19,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -42,11 +45,14 @@
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_DOZING_DURATION,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.DOZING,
- )
+ animationFlow
+ .setup(
+ duration = TO_DOZING_DURATION,
+ edge = Edge.create(from = Scenes.Bouncer, to = DOZING),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING),
+ )
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
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 b1fa710..7ae4558 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,13 @@
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.model.Edge
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.scene.shared.model.Scenes
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
import javax.inject.Inject
@@ -49,11 +51,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
- animationFlow.setup(
- duration = TO_GONE_DURATION,
- from = PRIMARY_BOUNCER,
- to = GONE,
- )
+ animationFlow
+ .setup(
+ duration = TO_GONE_DURATION,
+ edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Gone),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE),
+ )
private var leaveShadeOpen: Boolean = false
private var willRunDismissFromKeyguard: Boolean = false
@@ -88,6 +93,7 @@
} else {
createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
}
+
private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
return transitionAnimation.sharedFlow(
duration = 200.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 2575041..7511101 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -19,9 +19,12 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,11 +42,14 @@
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
private val transitionAnimation =
- animationFlow.setup(
- duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN,
- )
+ animationFlow
+ .setup(
+ duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
+ )
+ .setupWithoutSceneContainer(
+ edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+ )
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
index c02478b..96ef7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
@@ -206,11 +206,11 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
allEntries.remove(key)
userEntries.remove(key)?.let {
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
@@ -246,7 +246,7 @@
// Only remove media when the profile is unavailable.
if (DEBUG) Log.d(TAG, "Removing $key after profile change")
userEntries.remove(key, data)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -261,7 +261,7 @@
userEntries.clear()
keyCopy.forEach {
if (DEBUG) Log.d(TAG, "Removing $it after user change")
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
allEntries.forEach { (key, data) ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3a831156..143d66b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -545,8 +545,8 @@
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -578,7 +578,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delay = 0L, userInitiated = false)
}
return
}
@@ -627,17 +627,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaEntries.remove(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- override fun dismissMediaData(key: String, delay: Long): Boolean {
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
val existed = mediaEntries[key] != null
backgroundExecutor.execute {
mediaEntries[key]?.let { mediaData ->
@@ -649,7 +649,10 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key = key, userInitiated = userInitiated) },
+ delay
+ )
return existed
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index ad70db5..88910f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -53,8 +53,8 @@
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
- remove(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -71,8 +71,8 @@
}
}
- override fun onKeyRemoved(key: String) {
- remove(key)
+ override fun onKeyRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
/**
@@ -92,10 +92,10 @@
}
}
- private fun remove(key: String) {
+ private fun remove(key: String, userInitiated: Boolean) {
entries.remove(key)?.let {
val listenersCopy = listeners.toSet()
- listenersCopy.forEach { it.onMediaDataRemoved(key) }
+ listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 5432a18..8d19ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -213,7 +213,7 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
val instanceId = mediaData.instanceId
mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
@@ -221,7 +221,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
@@ -270,7 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -288,7 +288,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
getKey(instanceId)?.let {
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 2331aa21..8099e59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -60,7 +60,7 @@
)
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delay: Long): Boolean
+ fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean
/**
* Called whenever the recommendation has been expired or removed by the user. This will remove
@@ -136,7 +136,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- override fun onMediaDataRemoved(key: String) {}
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 1d7c025..eed7752 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -498,8 +498,8 @@
* External listeners registered with [MediaCarouselInteractor.addListener] will be notified
* after the event propagates through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -531,7 +531,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delayMs = 0L, userInitiated = false)
}
return
}
@@ -580,17 +580,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaDataRepository.removeMediaEntry(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delayMs: Long): Boolean {
+ fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean {
val existed = mediaDataRepository.mediaEntries.value[key] != null
backgroundExecutor.execute {
mediaDataRepository.mediaEntries.value[key]?.let { mediaData ->
@@ -602,16 +602,19 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key, userInitiated = userInitiated) },
+ delayMs
+ )
return existed
}
/** Dismiss a media entry. Returns false if the corresponding key was not found. */
- fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean {
+ fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean {
val mediaEntries = mediaDataRepository.mediaEntries.value
val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId }
return if (filteredEntries.isNotEmpty()) {
- dismissMediaData(filteredEntries.keys.first(), delayMs)
+ dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated)
} else {
false
}
@@ -1579,7 +1582,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- fun onMediaDataRemoved(key: String) {}
+ fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 0e2814b..043fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -111,10 +111,10 @@
}
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
val token = entries.remove(key)
token?.stop()
- token?.let { listeners.forEach { it.onKeyRemoved(key) } }
+ token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
}
fun dump(pw: PrintWriter) {
@@ -136,7 +136,7 @@
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
/** Called when the notification was removed. */
- fun onKeyRemoved(key: String)
+ fun onKeyRemoved(key: String, userInitiated: Boolean)
}
private inner class Entry(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index b2a8f2e..b178d84 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -137,7 +137,7 @@
// farther and dismiss the media data so that media controls for the local session
// don't hang around while casting.
if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated = false)
}
}
}
@@ -151,11 +151,11 @@
backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
// Queue on background thread to ensure ordering of loaded and removed events is maintained.
backgroundExecutor.execute {
keyedTokens.remove(key)
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated)
}
}
@@ -174,8 +174,10 @@
}
}
- private fun dispatchMediaDataRemoved(key: String) {
- foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
+ private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) {
+ foregroundExecutor.execute {
+ listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) }
+ }
}
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 29f3967..fc31903 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -169,7 +169,7 @@
mediaListeners[key] = PlaybackStateListener(key, data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaListeners.remove(key)?.destroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index c888935..9e62300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -205,12 +205,12 @@
)
}
- override fun dismissMediaData(key: String, delay: Long): Boolean {
- return mediaDataProcessor.dismissMediaData(key, delay)
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
+ return mediaDataProcessor.dismissMediaData(key, delay, userInitiated)
}
fun removeMediaControl(instanceId: InstanceId, delay: Long) {
- mediaDataProcessor.dismissMediaData(instanceId, delay)
+ mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false)
}
override fun dismissSmartspaceRecommendation(key: String, delay: Long) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 9f2d132..1a0f582 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -21,9 +21,7 @@
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.media.session.MediaController
import android.media.session.MediaSession
-import android.media.session.PlaybackState
import android.provider.Settings
import android.util.Log
import com.android.internal.jank.Cuj
@@ -42,7 +40,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.kotlin.pairwiseBy
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
@@ -70,19 +67,6 @@
.map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
.distinctUntilChanged()
- val isStartedPlaying: Flow<Boolean> =
- mediaControl
- .map { mediaControl ->
- mediaControl?.token?.let { token ->
- MediaController(applicationContext, token).playbackState?.let {
- it.state == PlaybackState.STATE_PLAYING
- }
- }
- ?: false
- }
- .pairwiseBy(initialValue = false) { wasPlaying, isPlaying -> !wasPlaying && isPlaying }
- .distinctUntilChanged()
-
val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
fun removeMediaControl(
@@ -90,7 +74,8 @@
instanceId: InstanceId,
delayMs: Long
): Boolean {
- val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs)
+ val dismissed =
+ mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true)
if (!dismissed) {
Log.w(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 73fb558..fed93f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -260,44 +260,50 @@
}
SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id ->
- val button = viewHolder.getAction(id)
- val actionViewModel = viewModel.actionButtons[index]
- if (button.id == R.id.actionPrev) {
- actionViewModel?.let {
- viewController.setUpPrevButtonInfo(true, it.notVisibleValue)
- }
- } else if (button.id == R.id.actionNext) {
- actionViewModel?.let {
- viewController.setUpNextButtonInfo(true, it.notVisibleValue)
- }
+ val buttonView = viewHolder.getAction(id)
+ val buttonModel = viewModel.actionButtons[index]
+ if (buttonView.id == R.id.actionPrev) {
+ viewController.setUpPrevButtonInfo(
+ buttonModel.isEnabled,
+ buttonModel.notVisibleValue
+ )
+ } else if (buttonView.id == R.id.actionNext) {
+ viewController.setUpNextButtonInfo(
+ buttonModel.isEnabled,
+ buttonModel.notVisibleValue
+ )
}
- actionViewModel?.let { action ->
- val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler
- animHandler.tryExecute {
- if (animHandler.updateRebindId(action.rebindId)) {
+ val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler
+ animHandler.tryExecute {
+ if (buttonModel.isEnabled) {
+ if (animHandler.updateRebindId(buttonModel.rebindId)) {
animHandler.unregisterAll()
- animHandler.tryRegister(action.icon)
- animHandler.tryRegister(action.background)
+ animHandler.tryRegister(buttonModel.icon)
+ animHandler.tryRegister(buttonModel.background)
bindButtonCommon(
- button,
+ buttonView,
viewHolder.multiRippleView,
- action,
+ buttonModel,
viewController,
falsingManager,
)
}
- val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing
- setSemanticButtonVisibleAndAlpha(
- viewHolder.getAction(id),
- viewController.expandedLayout,
- viewController.collapsedLayout,
- visible,
- action.notVisibleValue,
- action.showInCollapsed
- )
+ } else {
+ animHandler.unregisterAll()
+ clearButton(buttonView)
}
+ val visible =
+ buttonModel.isEnabled &&
+ (buttonModel.isVisibleWhenScrubbing || !viewController.isScrubbing)
+ setSemanticButtonVisibleAndAlpha(
+ viewHolder.getAction(id),
+ viewController.expandedLayout,
+ viewController.collapsedLayout,
+ visible,
+ buttonModel.notVisibleValue,
+ buttonModel.showInCollapsed
+ )
}
- ?: clearButton(button)
}
} else {
// Hide buttons that only appear for semantic actions
@@ -309,22 +315,16 @@
// Set all generic buttons
genericButtons.forEachIndexed { index, button ->
if (index < viewModel.actionButtons.size) {
- viewModel.actionButtons[index]?.let { action ->
- bindButtonCommon(
- button,
- viewHolder.multiRippleView,
- action,
- viewController,
- falsingManager,
- )
- setVisibleAndAlpha(expandedSet, button.id, visible = true)
- setVisibleAndAlpha(
- collapsedSet,
- button.id,
- visible = action.showInCollapsed
- )
- }
- ?: clearButton(button)
+ val action = viewModel.actionButtons[index]
+ bindButtonCommon(
+ button,
+ viewHolder.multiRippleView,
+ action,
+ viewController,
+ falsingManager,
+ )
+ setVisibleAndAlpha(expandedSet, button.id, visible = true)
+ setVisibleAndAlpha(collapsedSet, button.id, visible = action.showInCollapsed)
} else {
// Hide any unused buttons
clearButton(button)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 45b68ca..19e3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -46,6 +46,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -73,6 +74,9 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD
@@ -142,6 +146,7 @@
private val secureSettings: SecureSettings,
private val mediaCarouselViewModel: MediaCarouselViewModel,
private val mediaViewControllerFactory: Provider<MediaViewController>,
+ private val sceneInteractor: SceneInteractor,
) : Dumpable {
/** The current width of the carousel */
var currentCarouselWidth: Int = 0
@@ -190,9 +195,11 @@
@VisibleForTesting
lateinit var settingsButton: View
private set
+
private val mediaContent: ViewGroup
@VisibleForTesting var pageIndicator: PageIndicator
private var needsReordering: Boolean = false
+ private var isUserInitiatedRemovalQueued: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
var shouldScrollToKey: Boolean = false
private var isRtl: Boolean = false
@@ -301,7 +308,11 @@
* It will be called when the container is out of view.
*/
lateinit var updateUserVisibility: () -> Unit
- lateinit var updateHostVisibility: () -> Unit
+ var updateHostVisibility: () -> Unit = {}
+ set(value) {
+ field = value
+ mediaCarouselViewModel.updateHostVisibility = value
+ }
private val isReorderingAllowed: Boolean
get() = visualStabilityProvider.isReorderingAllowed
@@ -338,6 +349,20 @@
configurationController.addCallback(configListener)
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
setUpListeners()
+ } else {
+ val visualStabilityCallback = OnReorderingAllowedListener {
+ mediaCarouselViewModel.onReorderingAllowed()
+
+ // Update user visibility so that no extra impression will be logged when
+ // activeMediaIndex resets to 0
+ if (this::updateUserVisibility.isInitialized) {
+ updateUserVisibility()
+ }
+
+ // Let's reset our scroll position
+ mediaCarouselScrollHandler.scrollToStart()
+ }
+ visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
}
mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
// The pageIndicator is not laid out yet when we get the current state update,
@@ -359,8 +384,6 @@
)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
mediaCarousel.repeatWhenAttached {
- mediaCarouselViewModel.onAttached()
- mediaCarouselScrollHandler.scrollToStart()
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
@@ -385,12 +408,15 @@
reorderAllPlayers(previousVisiblePlayerKey = null)
}
- keysNeedRemoval.forEach { removePlayer(it) }
+ keysNeedRemoval.forEach {
+ removePlayer(it, userInitiated = isUserInitiatedRemovalQueued)
+ }
if (keysNeedRemoval.size > 0) {
// Carousel visibility may need to be updated after late removals
updateHostVisibility()
}
keysNeedRemoval.clear()
+ isUserInitiatedRemovalQueued = false
// Update user visibility so that no extra impression will be logged when
// activeMediaIndex resets to 0
@@ -474,18 +500,18 @@
val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
if (canRemove && !Utils.useMediaResumption(context)) {
- // This view isn't playing, let's remove this! This happens e.g. when
- // dismissing/timing out a view. We still have the data around because
- // resumption could be on, but we should save the resources and release
- // this.
+ // This media control is both paused and timed out, and the resumption
+ // setting is off - let's remove it
if (isReorderingAllowed) {
- onMediaDataRemoved(key)
+ onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway)
} else {
+ isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway
keysNeedRemoval.add(key)
}
} else {
keysNeedRemoval.remove(key)
}
+ MediaPlayerData.isSwipedAway = false
}
override fun onSmartspaceMediaDataLoaded(
@@ -565,11 +591,12 @@
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
}
}
+ MediaPlayerData.isSwipedAway = false
}
- override fun onMediaDataRemoved(key: String) {
- debugLogger.logMediaRemoved(key)
- removePlayer(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ debugLogger.logMediaRemoved(key, userInitiated)
+ removePlayer(key, userInitiated = userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -579,9 +606,7 @@
if (!immediately) {
// Although it wasn't requested, we were able to process the removal
// immediately since reordering is allowed. So, notify hosts to update
- if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
- updateHostVisibility()
- }
+ updateHostVisibility()
}
} else {
keysNeedRemoval.add(key)
@@ -632,9 +657,13 @@
@VisibleForTesting
internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor
- .transition(to = GONE)
- .filter { it.transitionState == TransitionState.FINISHED }
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }
+ } else {
+ keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter {
+ it.transitionState == TransitionState.FINISHED
+ }
+ }
.collect {
showMediaCarousel()
updateHostVisibility()
@@ -646,7 +675,7 @@
internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor
- .transition(to = LOCKSCREEN)
+ .transition(Edge.create(to = LOCKSCREEN))
.filter { it.transitionState == TransitionState.FINISHED }
.collect {
if (!allowMediaPlayerOnLockScreen) {
@@ -734,6 +763,7 @@
}
}
viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+ controllerByViewModel[commonViewModel] = viewController
updateViewControllerToState(viewController, noAnimation = true)
updatePageIndicator()
if (
@@ -747,7 +777,6 @@
mediaCarouselScrollHandler.onPlayersChanged()
mediaFrame.requiresRemeasuring = true
commonViewModel.onAdded(commonViewModel)
- controllerByViewModel[commonViewModel] = viewController
}
private fun onUpdated(commonViewModel: MediaCommonViewModel) {
@@ -1033,7 +1062,8 @@
fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
- dismissRecommendation: Boolean = true
+ dismissRecommendation: Boolean = true,
+ userInitiated: Boolean = false,
): MediaControlPanel? {
if (key == MediaPlayerData.smartspaceMediaKey()) {
MediaPlayerData.smartspaceMediaData?.let {
@@ -1052,7 +1082,7 @@
if (dismissMediaData) {
// Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, delay = 0L)
+ mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated)
}
if (dismissRecommendation) {
// Inform the media manager of a potentially late dismissal
@@ -1512,7 +1542,8 @@
}
}
- private fun onSwipeToDismiss() {
+ @VisibleForTesting
+ fun onSwipeToDismiss() {
if (mediaFlags.isMediaControlsRefactorEnabled()) {
mediaCarouselViewModel.onSwipeToDismiss()
return
@@ -1531,6 +1562,7 @@
it.mIsImpressed = false
}
}
+ MediaPlayerData.isSwipedAway = true
logger.logSwipeDismiss()
mediaManager.onSwipeToDismiss()
}
@@ -1557,6 +1589,7 @@
"state: ${desiredHostState?.expansion}, " +
"only active ${desiredHostState?.showsOnlyActiveMedia}"
)
+ println("isSwipedAway: ${MediaPlayerData.isSwipedAway}")
}
}
}
@@ -1587,6 +1620,7 @@
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
+
internal var smartspaceMediaData: SmartspaceMediaData? = null
private set
@@ -1595,7 +1629,7 @@
val data: MediaData,
val key: String,
val updateTime: Long = 0,
- val isSsReactivated: Boolean = false
+ val isSsReactivated: Boolean = false,
)
private val comparator =
@@ -1620,6 +1654,9 @@
// A map that tracks order of visible media players before they get reordered.
private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
+ // Whether the user swiped away the carousel since its last update
+ internal var isSwipedAway: Boolean = false
+
fun addMediaPlayer(
key: String,
data: MediaData,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index ebf1c6a..1be25a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -53,8 +53,16 @@
{ "add player $str1, active: $bool1" }
)
- fun logMediaRemoved(key: String) =
- buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
+ fun logMediaRemoved(key: String, userInitiated: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = userInitiated
+ },
+ { "removing player $str1, by user $bool1" }
+ )
fun logRecommendationLoaded(key: String, isActive: Boolean) =
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e6c785e..0bc3c439 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -786,10 +786,11 @@
if (mKey != null) {
closeGuts();
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
- MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
+ /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100,
+ /* userInitiated */ true)) {
Log.w(TAG, "Manager failed to dismiss media " + mKey);
// Remove directly from carousel so user isn't stuck with defunct controls
- mMediaCarouselController.removePlayer(mKey, false, false);
+ mMediaCarouselController.removePlayer(mKey, false, false, true);
}
} else {
Log.w(TAG, "Dismiss media with null notification. Token uid="
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 2b59858..3837708 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -709,12 +709,6 @@
// For Turbulence noise.
val loadingEffectView = mediaViewHolder.loadingEffectView
- turbulenceNoiseAnimationConfig =
- createTurbulenceNoiseConfig(
- loadingEffectView,
- turbulenceNoiseView,
- colorSchemeTransition
- )
noiseDrawCallback =
object : PaintDrawCallback {
override fun onDraw(paint: Paint) {
@@ -809,6 +803,14 @@
fun setUpTurbulenceNoise() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
+ turbulenceNoiseAnimationConfig =
+ createTurbulenceNoiseConfig(
+ mediaViewHolder.loadingEffectView,
+ mediaViewHolder.turbulenceNoiseView,
+ colorSchemeTransition
+ )
+ }
if (Flags.shaderlibLoadingEffectRefactor()) {
if (!this::loadingEffect.isInitialized) {
loadingEffect =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index eca76b6..91050c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -105,7 +105,7 @@
updateViewVisibility()
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateViewVisibility()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index fd5f445..4e90936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -57,12 +57,12 @@
val mediaItems: StateFlow<List<MediaCommonViewModel>> =
interactor.currentMedia
.map { sortedItems ->
- buildList {
+ val mediaList = buildList {
sortedItems.forEach { commonModel ->
// When view is started we should make sure to clean models that are pending
// removal.
// This action should only be triggered once.
- if (!isAttached || !modelsPendingRemoval.contains(commonModel)) {
+ if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) {
when (commonModel) {
is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
is MediaCommonModel.MediaRecommendations ->
@@ -70,11 +70,16 @@
}
}
}
- if (isAttached) {
- modelsPendingRemoval.clear()
- }
- isAttached = false
}
+ if (allowReorder) {
+ if (modelsPendingRemoval.size > 0) {
+ updateHostVisibility()
+ }
+ modelsPendingRemoval.clear()
+ }
+ allowReorder = false
+
+ mediaList
}
.stateIn(
scope = applicationScope,
@@ -82,6 +87,8 @@
initialValue = emptyList(),
)
+ var updateHostVisibility: () -> Unit = {}
+
private val mediaControlByInstanceId =
mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
@@ -89,15 +96,15 @@
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
- private var isAttached = false
+ private var allowReorder = false
fun onSwipeToDismiss() {
logger.logSwipeDismiss()
interactor.onSwipeToDismiss()
}
- fun onAttached() {
- isAttached = true
+ fun onReorderingAllowed() {
+ allowReorder = true
interactor.reorderMedia()
}
@@ -194,7 +201,11 @@
) {
if (immediatelyRemove || isReorderingAllowed()) {
interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
- // TODO if not immediate remove update host visibility
+ if (!immediatelyRemove) {
+ // Although it wasn't requested, we were able to process the removal
+ // immediately since reordering is allowed. So, notify hosts to update
+ updateHostVisibility()
+ }
} else {
modelsPendingRemoval.add(commonModel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index bc364c3..1944f07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -20,6 +20,7 @@
import android.content.pm.PackageManager
import android.media.session.MediaController
import android.media.session.MediaSession.Token
+import android.media.session.PlaybackState
import android.text.TextUtils
import android.util.Log
import androidx.constraintlayout.widget.ConstraintSet
@@ -40,16 +41,14 @@
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.res.R
-import com.android.systemui.util.kotlin.sample
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
/** Models UI state and handles user input for a media control. */
class MediaControlViewModel(
@@ -60,31 +59,20 @@
private val logger: MediaUiEventLogger,
) {
- private val isAnyButtonClicked: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
- private val playTurbulenceNoise: Flow<Boolean> =
- interactor.mediaControl.sample(
- combine(isAnyButtonClicked, interactor.isStartedPlaying) {
- isButtonClicked,
- isStartedPlaying ->
- isButtonClicked && isStartedPlaying
- }
- .distinctUntilChanged()
- )
-
@OptIn(ExperimentalCoroutinesApi::class)
val player: Flow<MediaPlayerViewModel?> =
interactor.onAnyMediaConfigurationChange
.flatMapLatest {
- combine(playTurbulenceNoise, interactor.mediaControl) {
- playTurbulenceNoise,
- mediaControl ->
- mediaControl?.let { toViewModel(it, playTurbulenceNoise) }
+ interactor.mediaControl.map { mediaControl ->
+ mediaControl?.let { toViewModel(it) }
}
}
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
+ private var isPlaying = false
+ private var isAnyButtonClicked = false
+
private fun onDismissMediaData(
token: Token?,
uid: Int,
@@ -95,10 +83,8 @@
interactor.removeMediaControl(token, instanceId, MEDIA_PLAYER_ANIMATION_DELAY)
}
- private suspend fun toViewModel(
- model: MediaControlModel,
- playTurbulenceNoise: Boolean
- ): MediaPlayerViewModel? {
+ private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? {
+ val mediaController = model.token?.let { MediaController(applicationContext, it) }
val wallpaperColors =
MediaArtworkHelper.getWallpaperColor(
applicationContext,
@@ -118,8 +104,14 @@
val gutsViewModel = toGutsViewModel(model, scheme)
+ // Set playing state
+ val wasPlaying = isPlaying
+ isPlaying =
+ mediaController?.playbackState?.let { it.state == PlaybackState.STATE_PLAYING } ?: false
+
// Resetting button clicks state.
- isAnyButtonClicked.value = false
+ val wasButtonClicked = isAnyButtonClicked
+ isAnyButtonClicked = false
return MediaPlayerViewModel(
contentDescription = { gutsVisible ->
@@ -144,7 +136,7 @@
shouldAddGradient = wallpaperColors != null,
colorScheme = scheme,
canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons),
- playTurbulenceNoise = playTurbulenceNoise,
+ playTurbulenceNoise = isPlaying && !wasPlaying && wasButtonClicked,
useSemanticActions = model.semanticActionButtons != null,
actionButtons = toActionViewModels(model),
outputSwitcher = toOutputSwitcherViewModel(model),
@@ -168,9 +160,7 @@
seekBarViewModel.updateStaticProgress(model.resumeProgress)
} else {
backgroundExecutor.execute {
- seekBarViewModel.updateController(
- model.token?.let { MediaController(applicationContext, it) }
- )
+ seekBarViewModel.updateController(mediaController)
}
}
}
@@ -283,16 +273,17 @@
)
}
- private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel?> {
+ private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel> {
val semanticActionButtons =
model.semanticActionButtons?.let { mediaButton ->
- with(mediaButton) {
- val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton)
- SEMANTIC_ACTIONS_ALL.map { buttonId ->
- getActionById(buttonId)?.let {
- toSemanticActionViewModel(model, it, buttonId, isScrubbingTimeEnabled)
- }
- }
+ val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton)
+ SEMANTIC_ACTIONS_ALL.map { buttonId ->
+ toSemanticActionViewModel(
+ model,
+ mediaButton.getActionById(buttonId),
+ buttonId,
+ isScrubbingTimeEnabled
+ )
}
}
val notifActionButtons =
@@ -304,7 +295,7 @@
private fun toSemanticActionViewModel(
model: MediaControlModel,
- mediaAction: MediaAction,
+ mediaAction: MediaAction?,
buttonId: Int,
canShowScrubbingTimeViews: Boolean
): MediaActionViewModel {
@@ -312,9 +303,9 @@
val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing
return MediaActionViewModel(
- icon = mediaAction.icon,
- contentDescription = mediaAction.contentDescription,
- background = mediaAction.background,
+ icon = mediaAction?.icon,
+ contentDescription = mediaAction?.contentDescription,
+ background = mediaAction?.background,
isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
notVisibleValue =
if (
@@ -326,11 +317,11 @@
ConstraintSet.GONE
},
showInCollapsed = showInCollapsed,
- rebindId = mediaAction.rebindId,
+ rebindId = mediaAction?.rebindId,
buttonId = buttonId,
- isEnabled = mediaAction.action != null,
+ isEnabled = mediaAction?.action != null,
onClicked = { id ->
- mediaAction.action?.let {
+ mediaAction?.action?.let {
onButtonClicked(id, model.uid, model.packageName, model.instanceId, it)
}
},
@@ -366,7 +357,7 @@
) {
logger.logTapAction(id, uid, packageName, instanceId)
// TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT)
- isAnyButtonClicked.value = true
+ isAnyButtonClicked = true
action.run()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index d1014e8..4334341 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -35,7 +35,7 @@
val canShowTime: Boolean,
val playTurbulenceNoise: Boolean,
val useSemanticActions: Boolean,
- val actionButtons: List<MediaActionViewModel?>,
+ val actionButtons: List<MediaActionViewModel>,
val outputSwitcher: MediaOutputSwitcherViewModel,
val gutsMenu: GutsViewModel,
val onClicked: (Expandable) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 88a5f78..061e7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -48,7 +48,7 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia();
if (DEBUG) {
Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia="
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 89e4760..a144dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -29,6 +29,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.Lazy
import javax.inject.Inject
@@ -48,7 +49,7 @@
* Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
* or if the flag value doesn't need to be overridden.
*/
- fun flagValueOverride(flag: Int): Boolean? {
+ fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
if (!SceneContainerFlag.isEnabled) {
return null
}
@@ -79,7 +80,7 @@
* to be overridden by the scene framework.
*/
val EvaluatorByFlag =
- mapOf<Int, (SceneContainerPluginState) -> Boolean>(
+ mapOf<Long, (SceneContainerPluginState) -> Boolean>(
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone },
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
{
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 2dd2327..67fe0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import dalvik.annotation.optimization.NeverCompile;
@@ -42,10 +43,10 @@
private final DisplayTracker mDisplayTracker;
private final SceneContainerPlugin mSceneContainerPlugin;
- private @QuickStepContract.SystemUiStateFlags int mFlags;
+ private @SystemUiStateFlags long mFlags;
private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
- private int mFlagsToSet = 0;
- private int mFlagsToClear = 0;
+ private long mFlagsToSet = 0;
+ private long mFlagsToClear = 0;
public SysUiState(DisplayTracker displayTracker, SceneContainerPlugin sceneContainerPlugin) {
mDisplayTracker = displayTracker;
@@ -67,12 +68,17 @@
}
/** Returns the current sysui state flags. */
- public int getFlags() {
+ @SystemUiStateFlags
+ public long getFlags() {
return mFlags;
}
+ public boolean isFlagEnabled(@SystemUiStateFlags long flag) {
+ return (mFlags & flag) != 0;
+ }
+
/** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
- public SysUiState setFlag(int flag, boolean enabled) {
+ public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) {
final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag);
if (overrideOrNull != null && enabled != overrideOrNull) {
if (DEBUG) {
@@ -91,7 +97,7 @@
return this;
}
- /** Call to save all the flags updated from {@link #setFlag(int, boolean)}. */
+ /** Call to save all the flags updated from {@link #setFlag(long, boolean)}. */
public void commitUpdate(int displayId) {
updateFlags(displayId);
mFlagsToSet = 0;
@@ -105,14 +111,14 @@
return;
}
- int newState = mFlags;
+ long newState = mFlags;
newState |= mFlagsToSet;
newState &= ~mFlagsToClear;
notifyAndSetSystemUiStateChanged(newState, mFlags);
}
/** Notify all those who are registered that the state has changed. */
- private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) {
+ private void notifyAndSetSystemUiStateChanged(long newFlags, long oldFlags) {
if (DEBUG) {
Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags);
}
@@ -137,7 +143,7 @@
/** Callback to be notified whenever system UI state flags are changed. */
public interface SysUiStateCallback{
/** To be called when any SysUiStateFlag gets updated */
- void onSystemUiStateChanged(@QuickStepContract.SystemUiStateFlags int sysUiFlags);
+ void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
index 5c49156..1e18f24 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -40,7 +40,7 @@
*/
fun SysUiState.updateFlags(
@DisplayId displayId: Int,
- vararg flagValuePairs: Pair<Int, Boolean>,
+ vararg flagValuePairs: Pair<Long, Boolean>,
) {
flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
commitUpdate(displayId)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a6b6d61..80c4379 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -128,7 +128,7 @@
private boolean mLongPressHomeEnabled;
private boolean mAssistantTouchGestureEnabled;
private int mNavBarMode;
- private int mA11yButtonState;
+ private long mA11yButtonState;
private int mRotationWatcherRotation;
private boolean mTogglingNavbarTaskbar;
private boolean mWallpaperVisible;
@@ -374,7 +374,7 @@
* {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
*/
private void updateA11yState() {
- final int prevState = mA11yButtonState;
+ final long prevState = mA11yButtonState;
final boolean clickable;
final boolean longClickable;
if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
@@ -431,7 +431,7 @@
* 48 = the combination of {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
* {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
*/
- public int getA11yButtonState() {
+ public long getA11yButtonState() {
return mA11yButtonState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 906ebad..0e819c2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1602,7 +1602,7 @@
void updateAccessibilityStateFlags() {
mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
if (mView != null) {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
mView.setAccessibilityButtonState(clickable, longClickable);
@@ -1611,7 +1611,7 @@
}
public void updateSystemUiStateFlags() {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f67973b..b360af0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -298,7 +298,7 @@
}
private void updateSysuiFlags() {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 295b293..9487085 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -270,7 +271,8 @@
private BackAnimation mBackAnimation;
private int mLeftInset;
private int mRightInset;
- private int mSysUiFlags;
+ @SystemUiStateFlags
+ private long mSysUiFlags;
// For Tf-Lite model.
private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
@@ -334,7 +336,7 @@
private final SysUiState.SysUiStateCallback mSysUiStateCallback =
new SysUiState.SysUiStateCallback() {
@Override
- public void onSystemUiStateChanged(int sysUiFlags) {
+ public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
mSysUiFlags = sysUiFlags;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a72..5e6ee4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
package com.android.systemui.qrcodescanner.dagger
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -54,5 +62,24 @@
),
instanceId = uiEventLogger.getNewInstanceId(),
)
+
+ /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+ @Provides
+ @IntoMap
+ @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+ fun provideQRCodeScannerTileViewModel(
+ factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+ mapper: QRCodeScannerTileMapper,
+ stateInteractor: QRCodeScannerTileDataInteractor,
+ userActionInteractor: QRCodeScannerTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
index 2c8a5a4..1336d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -18,6 +18,7 @@
import android.service.quicksettings.Tile
import android.text.TextUtils
+import android.widget.Switch
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.CustomTile
import com.android.systemui.qs.nano.QsTileState
@@ -44,8 +45,8 @@
}
label?.let { state.label = it.toString() }
secondaryLabel?.let { state.secondaryLabel = it.toString() }
- if (this is QSTile.BooleanState) {
- state.booleanState = value
+ if (expandedAccessibilityClassName == Switch::class.java.name) {
+ state.booleanState = state.state == QsTileState.ACTIVE
}
return state
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d26ae0a..5d35a69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -42,6 +42,7 @@
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import android.widget.Button;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -502,6 +503,8 @@
if (state instanceof BooleanState) {
state.expandedAccessibilityClassName = Switch.class.getName();
((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+ } else {
+ state.expandedAccessibilityClassName = Button.class.getName();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 0696fbe..2cc3985 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -29,8 +29,10 @@
import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -63,6 +65,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridLayout(
+ gridLayout: StretchedGridLayout
+ ): Pair<GridLayoutType, GridLayout> {
+ return Pair(StretchedGridLayoutType, gridLayout)
+ }
+
+ @Provides
fun provideGridLayoutMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
): Map<GridLayoutType, GridLayout> {
@@ -70,6 +80,13 @@
}
@Provides
+ fun provideGridLayoutTypes(
+ entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
+ ): Set<GridLayoutType> {
+ return entries.map { it.first }.toSet()
+ }
+
+ @Provides
@IntoSet
fun provideGridConsistencyInteractor(
consistencyInteractor: InfiniteGridConsistencyInteractor
@@ -78,6 +95,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridConsistencyInteractor(
+ consistencyInteractor: NoopGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(StretchedGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
fun provideGridConsistencyInteractorMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
): Map<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 542d0cb..31795d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -26,10 +26,17 @@
interface GridLayoutTypeRepository {
val layout: StateFlow<GridLayoutType>
+ fun setLayout(type: GridLayoutType)
}
@SysUISingleton
class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
override val layout = _layout.asStateFlow()
+
+ override fun setLayout(type: GridLayoutType) {
+ if (_layout.value != type) {
+ _layout.value = type
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index b6be578..4af1b22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -20,9 +20,13 @@
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
@SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) {
- val layout: Flow<GridLayoutType> = repo.layout
+class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
+ val layout: StateFlow<GridLayoutType> = repo.layout
+
+ fun setLayoutType(type: GridLayoutType) {
+ repo.setLayout(type)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 74e906c..b437f64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -18,6 +18,8 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -35,7 +37,7 @@
*/
override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
val newTiles: MutableList<TileSpec> = mutableListOf()
- val row = TileRow(columns = gridSizeInteractor.columns.value)
+ val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
val tilesQueue =
ArrayDeque(
@@ -54,7 +56,7 @@
while (tilesQueue.isNotEmpty()) {
if (row.isFull()) {
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
row.clear()
}
@@ -66,13 +68,13 @@
// We'll try to either add an icon tile from the queue to complete the row, or
// remove an icon tile from the current row to free up space.
- val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+ val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
if (iconTile != null) {
tilesQueue.remove(iconTile)
tilesQueue.addFirst(tile)
row.maybeAddTile(iconTile)
} else {
- val tileToRemove: SizedTile? = row.findLastIconTile()
+ val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
if (tileToRemove != null) {
row.removeTile(tileToRemove)
row.maybeAddTile(tile)
@@ -84,7 +86,7 @@
// If the row does not have an icon tile, add the incomplete row.
// Note: this shouldn't happen because an icon tile is guaranteed to be in a
// row that doesn't have enough space for a large tile.
- val tileSpecs = row.tileSpecs()
+ val tileSpecs = row.tiles.map { it.tile }
Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
newTiles.addAll(tileSpecs)
row.clear()
@@ -95,48 +97,11 @@
}
// Add last row that might be incomplete
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
return newTiles.toList()
}
- /** Tile with a width representing the number of columns it should take. */
- private data class SizedTile(val spec: TileSpec, val width: Int)
-
- private class TileRow(private val columns: Int) {
- private var availableColumns = columns
- private val tiles: MutableList<SizedTile> = mutableListOf()
-
- fun tileSpecs(): List<TileSpec> {
- return tiles.map { it.spec }
- }
-
- fun maybeAddTile(tile: SizedTile): Boolean {
- if (availableColumns - tile.width >= 0) {
- tiles.add(tile)
- availableColumns -= tile.width
- return true
- }
- return false
- }
-
- fun findLastIconTile(): SizedTile? {
- return tiles.findLast { it.width == 1 }
- }
-
- fun removeTile(tile: SizedTile) {
- tiles.remove(tile)
- availableColumns += tile.width
- }
-
- fun clear() {
- tiles.clear()
- availableColumns = columns
- }
-
- fun isFull(): Boolean = availableColumns == 0
- }
-
private companion object {
const val TAG = "InfiniteGridConsistencyInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
new file mode 100644
index 0000000..97ceacc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.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.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index 23110dc..501730a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -25,3 +25,9 @@
/** Grid type representing a scrollable vertical grid. */
data object InfiniteGridLayoutType : GridLayoutType
+
+/**
+ * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
+ * spaces.
+ */
+data object StretchedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
new file mode 100644
index 0000000..7e4381b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.shared.model
+
+/** Represents a tile of type [T] associated with a width */
+data class SizedTile<T>(val tile: T, val width: Int)
+
+/** Represents a row of [SizedTile] with a maximum width of [columns] */
+class TileRow<T>(private val columns: Int) {
+ private var availableColumns = columns
+ private val _tiles: MutableList<SizedTile<T>> = mutableListOf()
+ val tiles: List<SizedTile<T>>
+ get() = _tiles.toList()
+
+ fun maybeAddTile(tile: SizedTile<T>): Boolean {
+ if (availableColumns - tile.width >= 0) {
+ _tiles.add(tile)
+ availableColumns -= tile.width
+ return true
+ }
+ return false
+ }
+
+ fun findLastIconTile(): SizedTile<T>? {
+ return _tiles.findLast { it.width == 1 }
+ }
+
+ fun removeTile(tile: SizedTile<T>) {
+ _tiles.remove(tile)
+ availableColumns += tile.width
+ }
+
+ fun clear() {
+ _tiles.clear()
+ availableColumns = columns
+ }
+
+ fun isFull(): Boolean = availableColumns == 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
index 5c17fd1..3bda775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
@@ -20,9 +20,9 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
@Composable
@@ -30,8 +30,8 @@
viewModel: EditModeViewModel,
modifier: Modifier = Modifier,
) {
- val gridLayout by viewModel.gridLayout.collectAsState()
- val tiles by viewModel.tiles.collectAsState(emptyList())
+ val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle()
+ val tiles by viewModel.tiles.collectAsStateWithLifecycle(emptyList())
BackHandler { viewModel.stopEditing() }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index dc43091..f5ee720 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,85 +16,23 @@
package com.android.systemui.qs.panels.ui.compose
-import android.graphics.drawable.Animatable
-import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Arrangement.spacedBy
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Remove
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.Expandable
-import com.android.compose.theme.colorAttr
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.load
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
@SysUISingleton
class InfiniteGridLayout
@@ -104,8 +42,6 @@
private val gridSizeInteractor: InfiniteGridSizeInteractor
) : GridLayout {
- private object TileType
-
@Composable
override fun TileGrid(
tiles: List<TileViewModel>,
@@ -116,8 +52,8 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState()
- val columns by gridSizeInteractor.columns.collectAsState()
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
items(
@@ -140,55 +76,6 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
- @Composable
- private fun Tile(
- tile: TileViewModel,
- iconOnly: Boolean,
- modifier: Modifier,
- ) {
- val state: TileUiState by
- tile.state
- .mapLatest { it.toUiState() }
- .collectAsState(initial = tile.currentState.toUiState())
- val context = LocalContext.current
-
- Expandable(
- color = colorAttr(state.colors.background),
- shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
- ) {
- Row(
- modifier =
- modifier
- .combinedClickable(
- onClick = { tile.onClick(it) },
- onLongClick = { tile.onLongClick(it) }
- )
- .tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly),
- ) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
- }
- }
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel?.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
- }
- }
- }
-
@Composable
override fun EditTileGrid(
tiles: List<EditTileViewModel>,
@@ -196,259 +83,16 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, POSITION_AT_END)
- }
- val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
- val isIconOnly: (TileSpec) -> Boolean =
- remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
- val columns by gridSizeInteractor.columns.collectAsState()
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- // These Text are just placeholders to see the different sections. Not final UI.
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Current tiles", color = Color.White)
- }
-
- editTiles(
- currentTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- isIconOnly,
- indicatePosition = true,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
-
- editTiles(
- otherTilesStock,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Custom tiles to add", color = Color.White)
- }
-
- editTiles(
- otherTilesCustom,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
- }
- }
-
- private fun LazyGridScope.editTiles(
- tiles: List<EditTileViewModel>,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- indicatePosition: Boolean = false,
- ) {
- items(
- count = tiles.size,
- key = { tiles[it].tileSpec.spec },
- span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
- contentType = { TileType }
- ) {
- val viewModel = tiles[it]
- val canClick =
- when (clickAction) {
- ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
- ClickAction.REMOVE ->
- AvailableEditActions.REMOVE in viewModel.availableEditActions
- }
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD ->
- stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
- } else {
- ""
- }
-
- Box(
- modifier =
- Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
- .animateItem()
- .semantics {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
- ) {
- EditTile(
- tileViewModel = viewModel,
- isIconOnly(viewModel.tileSpec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
- if (canClick) {
- Badge(clickAction, Modifier.align(Alignment.TopEnd))
- }
- }
- }
- }
-
- @Composable
- private fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
- Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
- Icon(
- imageVector =
- when (action) {
- ClickAction.ADD -> Icons.Filled.Add
- ClickAction.REMOVE -> Icons.Filled.Remove
- },
- "",
- tint = Color.Black,
- )
- }
- }
-
- @Composable
- private fun EditTile(
- tileViewModel: EditTileViewModel,
- iconOnly: Boolean,
- modifier: Modifier = Modifier,
- ) {
- val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
- val colors = ActiveTileColorAttributes
-
- Row(
- modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
- ) {
- TileContent(
- label = label,
- secondaryLabel = tileViewModel.appName?.load(),
- colors = colors,
- icon = tileViewModel.icon,
- iconOnly = iconOnly,
- animateIconToEnd = true,
- )
- }
- }
-
- private enum class ClickAction {
- ADD,
- REMOVE,
- }
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(
- icon: Icon,
- color: Color,
- animateToEnd: Boolean = false,
-) {
- val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
- val context = LocalContext.current
- val loadedDrawable =
- remember(icon, context) {
- when (icon) {
- is Icon.Loaded -> icon.drawable
- is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
- }
- }
- if (loadedDrawable !is Animatable) {
- Icon(
- icon = icon,
- tint = color,
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
- val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
- }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
- }
- Image(
- painter = painter,
- contentDescription = null,
- colorFilter = ColorFilter.tint(color = color),
- modifier = modifier
- )
- }
-}
-
-@Composable
-private fun TileLazyGrid(
- modifier: Modifier = Modifier,
- columns: GridCells,
- content: LazyGridScope.() -> Unit,
-) {
- LazyVerticalGrid(
- columns = columns,
- verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
- horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
- modifier = modifier,
- content = content,
- )
-}
-
-@Composable
-private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
- return fillMaxWidth()
- .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
- .background(colorAttr(colors.background))
- .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
-}
-
-@Composable
-private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
- val horizontalAlignment =
- if (iconOnly) {
- Alignment.CenterHorizontally
- } else {
- Alignment.Start
- }
- return spacedBy(
- space = dimensionResource(id = R.dimen.qs_label_container_margin),
- alignment = horizontalAlignment
- )
-}
-
-@Composable
-private fun TileContent(
- label: String,
- secondaryLabel: String?,
- icon: Icon,
- colors: TileColorAttributes,
- iconOnly: Boolean,
- animateIconToEnd: Boolean = false,
-) {
- TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
-
- if (!iconOnly) {
- Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
- Text(
- label,
- color = colorAttr(colors.label),
- modifier = Modifier.basicMarquee(),
- )
- if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
- secondaryLabel ?: "",
- color = colorAttr(colors.secondaryLabel),
- modifier = Modifier.basicMarquee(),
- )
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
new file mode 100644
index 0000000..ddd97c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StretchedGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor,
+) : GridLayout {
+
+ @Composable
+ override fun TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ ) {
+ DisposableEffect(tiles) {
+ val token = Any()
+ tiles.forEach { it.startListening(token) }
+ onDispose { tiles.forEach { it.stopListening(token) } }
+ }
+
+ // Tile widths [normal|stretched]
+ // Icon [3 | 4]
+ // Large [6 | 8]
+ val columns = 12
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val stretchedTiles =
+ remember(tiles) {
+ val sizedTiles =
+ tiles.map {
+ SizedTile(
+ it,
+ if (iconTilesSpecs.contains(it.spec)) {
+ 3
+ } else {
+ 6
+ }
+ )
+ }
+ splitInRows(sizedTiles, columns)
+ }
+
+ TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
+ items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
+ Tile(
+ stretchedTiles[index].tile,
+ iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+ Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun EditTileGrid(
+ tiles: List<EditTileViewModel>,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit
+ ) {
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
+ modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
+ )
+ }
+
+ private fun splitInRows(
+ tiles: List<SizedTile<TileViewModel>>,
+ columns: Int
+ ): List<SizedTile<TileViewModel>> {
+ val row = TileRow<TileViewModel>(columns)
+
+ return buildList {
+ for (tile in tiles) {
+ if (row.maybeAddTile(tile)) {
+ if (row.isFull()) {
+ // Row is full, no need to stretch tiles
+ addAll(row.tiles)
+ row.clear()
+ }
+ } else {
+ if (row.isFull()) {
+ addAll(row.tiles)
+ } else {
+ // Stretching tiles when row isn't full
+ addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
+ }
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ addAll(row.tiles)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
new file mode 100644
index 0000000..eb45110
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.theme.colorAttr
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapLatest
+
+object TileType
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@Composable
+fun Tile(
+ tile: TileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier,
+) {
+ val state: TileUiState by
+ tile.state
+ .mapLatest { it.toUiState() }
+ .collectAsStateWithLifecycle(tile.currentState.toUiState())
+ val context = LocalContext.current
+
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
+ ) {
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
+ }
+ }
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
+ }
+}
+
+@Composable
+fun TileLazyGrid(
+ modifier: Modifier = Modifier,
+ columns: GridCells,
+ content: LazyGridScope.() -> Unit,
+) {
+ LazyVerticalGrid(
+ columns = columns,
+ verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+ horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
+ modifier = modifier,
+ content = content,
+ )
+}
+
+@Composable
+fun DefaultEditTileGrid(
+ tiles: List<EditTileViewModel>,
+ iconOnlySpecs: Set<TileSpec>,
+ columns: GridCells,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit,
+) {
+ val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+ val isIconOnly: (TileSpec) -> Boolean =
+ remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+
+ TileLazyGrid(modifier = modifier, columns = columns) {
+ // These Text are just placeholders to see the different sections. Not final UI.
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+
+ editTiles(
+ currentTiles,
+ ClickAction.REMOVE,
+ onRemoveTile,
+ isIconOnly,
+ indicatePosition = true,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+
+ editTiles(
+ otherTilesStock,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) {
+ Text("Custom tiles to add", color = Color.White)
+ }
+
+ editTiles(
+ otherTilesCustom,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+ }
+}
+
+private fun LazyGridScope.editTiles(
+ tiles: List<EditTileViewModel>,
+ clickAction: ClickAction,
+ onClick: (TileSpec) -> Unit,
+ isIconOnly: (TileSpec) -> Boolean,
+ indicatePosition: Boolean = false,
+) {
+ items(
+ count = tiles.size,
+ key = { tiles[it].tileSpec.spec },
+ span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+ contentType = { TileType }
+ ) {
+ val viewModel = tiles[it]
+ val canClick =
+ when (clickAction) {
+ ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
+ ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions
+ }
+ val onClickActionName =
+ when (clickAction) {
+ ClickAction.ADD ->
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ ClickAction.REMOVE ->
+ stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+ }
+ val stateDescription =
+ if (indicatePosition) {
+ stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
+ } else {
+ ""
+ }
+
+ Box(
+ modifier =
+ Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
+ .animateItem()
+ .semantics {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ ) {
+ EditTile(
+ tileViewModel = viewModel,
+ isIconOnly(viewModel.tileSpec),
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ if (canClick) {
+ Badge(clickAction, Modifier.align(Alignment.TopEnd))
+ }
+ }
+ }
+}
+
+@Composable
+fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
+ Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
+ Icon(
+ imageVector =
+ when (action) {
+ ClickAction.ADD -> Icons.Filled.Add
+ ClickAction.REMOVE -> Icons.Filled.Remove
+ },
+ "",
+ tint = Color.Black,
+ )
+ }
+}
+
+@Composable
+fun EditTile(
+ tileViewModel: EditTileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
+ val colors = ActiveTileColorAttributes
+
+ Row(
+ modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ ) {
+ TileContent(
+ label = label,
+ secondaryLabel = tileViewModel.appName?.load(),
+ colors = colors,
+ icon = tileViewModel.icon,
+ iconOnly = iconOnly,
+ animateIconToEnd = true,
+ )
+ }
+}
+
+enum class ClickAction {
+ ADD,
+ REMOVE,
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+private fun TileIcon(
+ icon: Icon,
+ color: Color,
+ animateToEnd: Boolean = false,
+) {
+ val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+ val context = LocalContext.current
+ val loadedDrawable =
+ remember(icon, context) {
+ when (icon) {
+ is Icon.Loaded -> icon.drawable
+ is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+ }
+ }
+ if (loadedDrawable !is Animatable) {
+ Icon(
+ icon = icon,
+ tint = color,
+ modifier = modifier,
+ )
+ } else if (icon is Icon.Resource) {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ val painter =
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ }
+ Image(
+ painter = painter,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(color = color),
+ modifier = modifier
+ )
+ }
+}
+
+@Composable
+private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
+ return fillMaxWidth()
+ .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
+ .background(colorAttr(colors.background))
+ .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
+}
+
+@Composable
+private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
+ val horizontalAlignment =
+ if (iconOnly) {
+ Alignment.CenterHorizontally
+ } else {
+ Alignment.Start
+ }
+ return spacedBy(
+ space = dimensionResource(id = R.dimen.qs_label_container_margin),
+ alignment = horizontalAlignment
+ )
+}
+
+@Composable
+private fun TileContent(
+ label: String,
+ secondaryLabel: String?,
+ icon: Icon,
+ colors: TileColorAttributes,
+ iconOnly: Boolean,
+ animateIconToEnd: Boolean = false,
+) {
+ TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+ if (!iconOnly) {
+ Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+ Text(
+ label,
+ color = colorAttr(colors.label),
+ modifier = Modifier.basicMarquee(),
+ )
+ if (!TextUtils.isEmpty(secondaryLabel)) {
+ Text(
+ secondaryLabel ?: "",
+ color = colorAttr(colors.secondaryLabel),
+ modifier = Modifier.basicMarquee(),
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
index 2f32d72..2dab7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
@@ -17,15 +17,15 @@
package com.android.systemui.qs.panels.ui.compose
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
@Composable
fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
- val gridLayout by viewModel.gridLayout.collectAsState()
- val tiles by viewModel.tileViewModels.collectAsState(emptyList())
+ val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle()
+ val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList())
gridLayout.TileGrid(tiles, modifier)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2068799..71b69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -32,9 +34,12 @@
import android.widget.Switch;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -54,6 +59,8 @@
import dagger.Lazy;
+import kotlinx.coroutines.Job;
+
import javax.inject.Inject;
/** Quick settings tile: Airplane mode **/
@@ -66,6 +73,9 @@
private final Lazy<ConnectivityManager> mLazyConnectivityManager;
private boolean mListening;
+ @Nullable
+ @VisibleForTesting
+ Job mClickJob;
@Inject
public AirplaneModeTile(
@@ -111,6 +121,21 @@
new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
return;
}
+
+ if (Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> {
+ if (isAllowClick) {
+ setEnabled(!airplaneModeEnabled);
+ }
+ return null;
+ });
+ return;
+ }
+
setEnabled(!airplaneModeEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9af34f6..9f41d98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.annotation.Nullable;
@@ -33,11 +34,14 @@
import android.util.Log;
import android.widget.Switch;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
@@ -55,6 +59,8 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
+import kotlinx.coroutines.Job;
+
import java.util.List;
import java.util.concurrent.Executor;
@@ -78,6 +84,9 @@
private final BluetoothTileDialogViewModel mDialogViewModel;
private final FeatureFlags mFeatureFlags;
+ @Nullable
+ @VisibleForTesting
+ Job mClickJob;
@Inject
public BluetoothTile(
@@ -110,6 +119,24 @@
@Override
protected void handleClick(@Nullable Expandable expandable) {
+ if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> {
+ if (!isAllowClick) {
+ return null;
+ }
+ handleClickEvent(expandable);
+ return null;
+ });
+ return;
+ }
+ handleClickEvent(expandable);
+ }
+
+ private void handleClickEvent(@Nullable Expandable expandable) {
if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
mDialogViewModel.showDialog(expandable);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a..972b20e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@
/**
* Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
*/
interface QSTileIntentUserInputHandler {
- fun handle(expandable: Expandable?, intent: Intent)
+ fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+ )
/** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
fun handle(
@@ -52,12 +56,25 @@
private val userHandle: UserHandle,
) : QSTileIntentUserInputHandler {
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
val animationController: ActivityTransitionAnimator.Controller? =
expandable?.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
)
- activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ if (dismissShadeShowOverLockScreenWhenLocked) {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ }
}
// TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c9c4443..c971f54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.tiles.dialog;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI;
import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
@@ -57,6 +58,8 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.Prefs;
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -73,6 +76,7 @@
import dagger.assisted.AssistedInject;
import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
import java.util.List;
import java.util.concurrent.Executor;
@@ -161,6 +165,9 @@
// Wi-Fi scanning progress bar
protected boolean mIsProgressBarVisible;
private SystemUIDialog mDialog;
+ private final CoroutineScope mCoroutineScope;
+ @Nullable
+ private Job mClickJob;
@AssistedFactory
public interface Factory {
@@ -203,7 +210,7 @@
mCanConfigWifi = canConfigWifi;
mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
mKeyguard = keyguardStateController;
-
+ mCoroutineScope = coroutineScope;
mUiEventLogger = uiEventLogger;
mDialogTransitionAnimator = dialogTransitionAnimator;
mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope);
@@ -388,11 +395,9 @@
});
mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
- mWiFiToggle.setOnCheckedChangeListener(
- (buttonView, isChecked) -> {
- if (mInternetDialogController.isWifiEnabled() == isChecked) return;
- mInternetDialogController.setWifiEnabled(isChecked);
- });
+ mWiFiToggle.setOnClickListener(v -> {
+ handleWifiToggleClicked(mWiFiToggle.isChecked());
+ });
mDoneButton.setOnClickListener(v -> dialog.dismiss());
mShareWifiButton.setOnClickListener(v -> {
if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) {
@@ -404,6 +409,32 @@
});
}
+ private void handleWifiToggleClicked(boolean isChecked) {
+ if (Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> {
+ if (isAllowClick) {
+ setWifiEnable(isChecked);
+ } else {
+ mWiFiToggle.setChecked(!isChecked);
+ }
+ return null;
+ });
+ return;
+ }
+ setWifiEnable(isChecked);
+ }
+
+ private void setWifiEnable(boolean isChecked) {
+ if (mInternetDialogController.isWifiEnabled() == isChecked) {
+ return;
+ }
+ mInternetDialogController.setWifiEnabled(isChecked);
+ }
+
@MainThread
private void updateEthernet() {
mEthernetLayout.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 0000000..1e8ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+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.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<QRCodeScannerTileModel> =
+ conflatedCallbackFlow {
+ qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySend(generateModel())
+ }
+ }
+ qrController.addCallback(callback)
+ awaitClose {
+ qrController.removeCallback(callback)
+ qrController.unregisterQRCodeScannerChangeObservers(
+ DEFAULT_QR_CODE_SCANNER_CHANGE
+ )
+ }
+ }
+ .onStart { emit(generateModel()) }
+ .flowOn(bgCoroutineContext)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ QRCodeScannerTileModel.TemporarilyUnavailable
+ )
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(qrController.isCameraAvailable)
+
+ private fun generateModel(): QRCodeScannerTileModel {
+ val intent = qrController.intent
+
+ return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+ QRCodeScannerTileModel.Available(intent)
+ else QRCodeScannerTileModel.TemporarilyUnavailable
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 0000000..7c0c41e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ when (data) {
+ is QRCodeScannerTileModel.Available ->
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ data.intent,
+ true
+ )
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+ }
+ }
+ is QSTileUserAction.LongClick -> {} // no-op
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
new file mode 100644
index 0000000..22c9b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.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.qs.tiles.impl.qr.domain.model
+
+import android.content.Intent
+
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+ data class Available(val intent: Intent) : QRCodeScannerTileModel
+ data object TemporarilyUnavailable : QRCodeScannerTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
new file mode 100644
index 0000000..45a7717
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+ override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.qr_code_scanner_title)
+ contentDescription = label
+ icon = {
+ Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+ }
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+ when (data) {
+ is QRCodeScannerTileModel.Available -> {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = null
+ }
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index faf2bbc..0327ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -108,15 +108,18 @@
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -128,8 +131,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import dagger.Lazy;
-
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -414,7 +415,13 @@
@Override
public void toggleNotificationPanel() {
verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
- mCommandQueue.togglePanel());
+ mCommandQueue.toggleNotificationsPanel());
+ }
+
+ @Override
+ public void toggleQuickSettingsPanel() {
+ verifyCallerAndClearCallingIdentityPostMain("toggleQuickSettingsPanel", () ->
+ mCommandQueue.toggleQuickSettingsPanel());
}
private boolean verifyCaller(String reason) {
@@ -765,7 +772,7 @@
}
}
- private void notifySystemUiStateFlags(int flags) {
+ private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
+ mOverviewProxy + " flags=" + flags);
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4e290e6..6694878 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -20,7 +20,6 @@
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
-import android.content.pm.LauncherApps
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
@@ -63,7 +62,6 @@
private val panelInteractor: PanelInteractor,
private val issueRecordingState: IssueRecordingState,
private val iActivityManager: IActivityManager,
- private val launcherApps: LauncherApps,
) :
RecordingService(
controller,
@@ -85,7 +83,7 @@
when (intent?.action) {
ACTION_START -> {
TraceUtils.traceStart(
- contentResolver,
+ this,
DEFAULT_TRACE_TAGS,
DEFAULT_BUFFER_SIZE,
DEFAULT_IS_INCLUDING_WINSCOPE,
@@ -104,11 +102,7 @@
}
ACTION_STOP,
ACTION_STOP_NOTIF -> {
- // ViewCapture needs to save it's data before it is disabled, or else the data will
- // be lost. This is expected to change in the near future, and when that happens
- // this line should be removed.
- launcherApps.saveViewCaptureData()
- TraceUtils.traceStop(contentResolver)
+ TraceUtils.traceStop(this)
issueRecordingState.isRecording = false
}
ACTION_SHARE -> {
@@ -142,7 +136,7 @@
private fun shareRecording(screenRecording: Uri?) {
val traces =
- TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+ TraceUtils.traceDump(this, TRACE_FILE_NAME).getOrElse {
Log.v(
TAG,
"Traces were not present. This can happen if users double" +
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index eabc42b..3e2c630 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -21,6 +21,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -36,6 +37,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
+@SysUISingleton
/** Source of truth for scene framework application state. */
class SceneContainerRepository
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 08efe39..0d0f6e0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -55,6 +55,18 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
+ interface OnSceneAboutToChangeListener {
+
+ /**
+ * Notifies that the scene is about to change to [toScene].
+ *
+ * The implementation can choose to consume the [sceneState] to prepare the incoming scene.
+ */
+ fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?)
+ }
+
+ private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
+
/**
* The current scene.
*
@@ -149,6 +161,10 @@
return repository.allSceneKeys()
}
+ fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
+ onSceneAboutToChangeListener.add(processor)
+ }
+
/**
* Requests a scene change to the given scene.
*
@@ -161,6 +177,7 @@
toScene: SceneKey,
loggingReason: String,
transitionKey: TransitionKey? = null,
+ sceneState: Any? = null,
) {
val currentSceneKey = currentScene.value
if (
@@ -180,6 +197,7 @@
isInstant = false,
)
+ onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) }
repository.changeScene(toScene, transitionKey)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 4a64277..3ce12dd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -234,7 +234,7 @@
bouncerInteractor.onImeHiddenByUser.collectLatest {
if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
+ toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState?
loggingReason = "IME hidden",
)
}
@@ -252,6 +252,7 @@
when {
isAnySimLocked -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Bouncer,
loggingReason = "Need to authenticate locked SIM card."
)
@@ -259,6 +260,7 @@
unlockStatus.isUnlocked &&
deviceEntryInteractor.canSwipeToEnter.value == false -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Gone,
loggingReason =
"All SIM cards unlocked and device already unlocked and " +
@@ -267,6 +269,7 @@
}
else -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Lockscreen,
loggingReason =
"All SIM cards unlocked and device still locked" +
@@ -325,7 +328,8 @@
Scenes.Gone to "device was unlocked in Bouncer scene"
} else {
val prevScene = previousScene.value
- (prevScene ?: Scenes.Gone) to
+ (prevScene
+ ?: Scenes.Gone) to
"device was unlocked in Bouncer scene, from sceneKey=$prevScene"
}
isOnLockscreen ->
@@ -364,6 +368,7 @@
powerInteractor.isAsleep.collect { isAsleep ->
if (isAsleep) {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Lockscreen,
loggingReason = "device is starting to sleep",
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
index caa67df..1868b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -25,7 +25,6 @@
import android.os.UserHandle
import android.util.Log
import android.util.Pair
-import android.view.View
import android.view.Window
import com.android.app.tracing.coroutines.launch
import com.android.internal.app.ChooserActivity
@@ -41,8 +40,8 @@
private val intentExecutor: ActionIntentExecutor,
@Application private val applicationScope: CoroutineScope,
@Assisted val window: Window,
- @Assisted val transitionView: View,
- @Assisted val onDismiss: (() -> Unit)
+ @Assisted val viewProxy: ScreenshotViewProxy,
+ @Assisted val finishDismiss: () -> Unit,
) {
var isPendingSharedTransition = false
@@ -50,6 +49,7 @@
fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
isPendingSharedTransition = true
+ viewProxy.fadeForSharedTransition()
val windowTransition = createWindowTransition()
applicationScope.launch("$TAG#launchIntentAsync") {
intentExecutor.launchIntent(
@@ -70,7 +70,7 @@
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
pendingIntent.send(options.toBundle())
- onDismiss.invoke()
+ viewProxy.requestDismissal(null)
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "Intent cancelled", e)
}
@@ -89,7 +89,7 @@
override fun hideSharedElements() {
isPendingSharedTransition = false
- onDismiss.invoke()
+ finishDismiss.invoke()
}
override fun onFinish() {}
@@ -98,13 +98,20 @@
window,
callbacks,
null,
- Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+ Pair.create(
+ viewProxy.screenshotPreview,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME
+ )
)
}
@AssistedFactory
interface Factory {
- fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+ fun create(
+ window: Window,
+ viewProxy: ScreenshotViewProxy,
+ finishDismiss: (() -> Unit)
+ ): ActionExecutor
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index a0cef52..15638d3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -97,6 +97,7 @@
.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
}
private const val EXTRA_EDIT_SOURCE = "edit_source"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 4eca51d..4ab0918 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -33,10 +33,11 @@
import android.view.WindowManagerGlobal
import com.android.app.tracing.coroutines.launch
import com.android.internal.infra.ServiceConnector
-import com.android.systemui.Flags.screenshotActionDismissSystemWindows
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.proxy.SystemUiProxy
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -54,8 +55,8 @@
private val activityManagerWrapper: ActivityManagerWrapper,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
+ private val systemUiProxy: SystemUiProxy,
private val displayTracker: DisplayTracker,
- private val keyguardController: ScreenshotKeyguardController,
) {
/**
* Execute the given intent with startActivity while performing operations for screenshot action
@@ -83,14 +84,12 @@
options: ActivityOptions?,
transitionCoordinator: ExitTransitionCoordinator?,
) {
- if (screenshotActionDismissSystemWindows()) {
- keyguardController.dismiss()
+ if (Flags.fixScreenshotActionDismissSystemWindows()) {
activityManagerWrapper.closeSystemWindows(
CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT
)
- } else {
- dismissKeyguard()
}
+ systemUiProxy.dismissKeyguard()
transitionCoordinator?.startExit()
if (user == myUserHandle()) {
@@ -110,27 +109,6 @@
}
}
- private val proxyConnector: ServiceConnector<IScreenshotProxy> =
- ServiceConnector.Impl(
- context,
- Intent(context, ScreenshotProxyService::class.java),
- Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
- context.userId,
- IScreenshotProxy.Stub::asInterface,
- )
-
- private suspend fun dismissKeyguard() {
- val completion = CompletableDeferred<Unit>()
- val onDoneBinder =
- object : IOnDoneCallback.Stub() {
- override fun onDone(success: Boolean) {
- completion.complete(Unit)
- }
- }
- proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
- completion.await()
- }
-
private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> =
ServiceConnector.Impl<ICrossProfileService>(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 4cf18fb..3d024a6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -157,6 +157,8 @@
override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+ override fun fadeForSharedTransition() {} // unused
+
override fun stopInputListening() = view.stopInputListening()
override fun requestFocus() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 2f026ae..9ad6d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -317,9 +317,9 @@
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
- mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
() -> {
- requestDismissal(null);
+ finishDismiss();
return Unit.INSTANCE;
});
@@ -623,9 +623,11 @@
(response) -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
- if (screenshotShelfUi2() && mActionsProvider != null) {
- mActionsProvider.onScrollChipReady(
- () -> onScrollButtonClicked(owner, response));
+ if (screenshotShelfUi2()) {
+ if (mActionsProvider != null) {
+ mActionsProvider.onScrollChipReady(
+ () -> onScrollButtonClicked(owner, response));
+ }
} else {
mViewProxy.showScrollChip(response.getPackageName(),
() -> onScrollButtonClicked(owner, response));
@@ -657,9 +659,7 @@
() -> {
final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
owner, mContext);
- mActionIntentExecutor.launchIntentAsync(intent, owner, true,
- ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
-
+ mActionIntentExecutor.launchIntentAsync(intent, owner, true, null, null);
},
mViewProxy::restoreNonScrollingUi,
mViewProxy::startLongScreenshotTransition);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
deleted file mode 100644
index 7696bbe..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
+++ /dev/null
@@ -1,46 +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.screenshot
-
-import android.content.Context
-import android.content.Intent
-import com.android.internal.infra.ServiceConnector
-import javax.inject.Inject
-import kotlinx.coroutines.CompletableDeferred
-
-open class ScreenshotKeyguardController @Inject constructor(context: Context) {
- private val proxyConnector: ServiceConnector<IScreenshotProxy> =
- ServiceConnector.Impl(
- context,
- Intent(context, ScreenshotProxyService::class.java),
- Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
- context.userId,
- IScreenshotProxy.Stub::asInterface
- )
-
- suspend fun dismiss() {
- val completion = CompletableDeferred<Unit>()
- val onDoneBinder =
- object : IOnDoneCallback.Stub() {
- override fun onDone(success: Boolean) {
- completion.complete(Unit)
- }
- }
- proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
- completion.await()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 1e66cd1..3ac070a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -31,6 +31,7 @@
import android.view.WindowManager
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.android.internal.logging.UiEventLogger
@@ -58,6 +59,7 @@
private val logger: UiEventLogger,
private val viewModel: ScreenshotViewModel,
private val windowManager: WindowManager,
+ shelfViewBinder: ScreenshotShelfViewBinder,
private val thumbnailObserver: ThumbnailObserver,
@Assisted private val context: Context,
@Assisted private val displayId: Int
@@ -69,7 +71,17 @@
override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
override var screenshot: ScreenshotData? = null
set(value) {
- viewModel.setScreenshotBitmap(value?.bitmap)
+ value?.let {
+ viewModel.setScreenshotBitmap(it.bitmap)
+ val badgeBg =
+ AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+ val user = it.userHandle
+ if (badgeBg != null && user != null) {
+ viewModel.setScreenshotBadge(
+ context.packageManager.getUserBadgedIcon(badgeBg, user)
+ )
+ }
+ }
field = value
}
@@ -78,15 +90,15 @@
override var isDismissing = false
override var isPendingSharedTransition = false
- private val animationController = ScreenshotAnimationController(view)
+ private val animationController = ScreenshotAnimationController(view, viewModel)
init {
- ScreenshotShelfViewBinder.bind(
+ shelfViewBinder.bind(
view,
viewModel,
+ animationController,
LayoutInflater.from(context),
onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
- onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() },
onUserInteraction = { callbacks?.onUserInteraction() }
)
view.updateInsets(windowManager.currentWindowMetrics.windowInsets)
@@ -176,24 +188,53 @@
override fun prepareScrollingTransition(
response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
+ screenBitmap: Bitmap, // unused
newScreenshot: Bitmap,
screenshotTakenInPortrait: Boolean,
onTransitionPrepared: Runnable,
) {
- onTransitionPrepared.run()
+ viewModel.setScrollingScrimBitmap(newScreenshot)
+ viewModel.setScrollableRect(scrollableAreaOnScreen(response))
+ animationController.fadeForLongScreenshotTransition()
+ view.post { onTransitionPrepared.run() }
+ }
+
+ private fun scrollableAreaOnScreen(response: ScrollCaptureResponse): Rect {
+ val r = Rect(response.boundsInWindow)
+ val windowInScreen = response.windowBounds
+ r.offset(windowInScreen?.left ?: 0, windowInScreen?.top ?: 0)
+ r.intersect(
+ Rect(
+ 0,
+ 0,
+ context.resources.displayMetrics.widthPixels,
+ context.resources.displayMetrics.heightPixels
+ )
+ )
+ return r
}
override fun startLongScreenshotTransition(
transitionDestination: Rect,
onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
+ longScreenshot: ScrollCaptureController.LongScreenshot,
) {
- onTransitionEnd.run()
- callbacks?.onDismiss()
+ val transitionAnimation =
+ animationController.runLongScreenshotTransition(
+ transitionDestination,
+ longScreenshot,
+ onTransitionEnd
+ )
+ transitionAnimation.doOnEnd { callbacks?.onDismiss() }
+ transitionAnimation.start()
}
- override fun restoreNonScrollingUi() {}
+ override fun restoreNonScrollingUi() {
+ viewModel.setScrollableRect(null)
+ viewModel.setScrollingScrimBitmap(null)
+ animationController.restoreUI()
+ callbacks?.onUserInteraction() // reset the timeout
+ }
override fun stopInputListening() {}
@@ -216,6 +257,10 @@
)
}
+ override fun fadeForSharedTransition() {
+ animationController.fadeForSharedTransition()
+ }
+
private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
val onBackInvokedCallback = OnBackInvokedCallback {
debugLog(DEBUG_INPUT) { "Predictive Back callback dispatched" }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index a4069d1..df93a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -63,6 +63,7 @@
longScreenshot: ScrollCaptureController.LongScreenshot
)
fun restoreNonScrollingUi()
+ fun fadeForSharedTransition()
fun stopInputListening()
fun requestFocus()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 5e561cf..ee1944e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -45,6 +45,7 @@
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.util.List;
@@ -378,8 +379,14 @@
upper = 1;
break;
}
- Log.i(TAG, "getAllowedValues: " + boundary + ", "
- + "result=[lower=" + lower + ", upper=" + upper + "]");
+ if (lower >= upper) {
+ Log.wtf(TAG, "getAllowedValues computed an invalid range "
+ + "[" + lower + ", " + upper + "]");
+ if (Flags.screenshotScrollCropViewCrashFix()) {
+ lower = Math.min(lower, upper);
+ upper = lower;
+ }
+ }
return new Range<>(lower, upper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index 06e88f4..a4906c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -20,6 +20,10 @@
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.Matrix
import android.graphics.PointF
import android.graphics.Rect
import android.util.MathUtils
@@ -29,13 +33,21 @@
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.sign
-class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
+class ScreenshotAnimationController(
+ private val view: ScreenshotShelfView,
+ private val viewModel: ScreenshotViewModel
+) {
private var animator: Animator? = null
private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
+ private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim))
+ private val scrollTransitionPreview =
+ view.requireViewById<ImageView>(R.id.screenshot_scrollable_preview)
private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
private val fastOutSlowIn =
@@ -46,6 +58,14 @@
view.requireViewById(R.id.screenshot_badge),
view.requireViewById(R.id.screenshot_dismiss_button)
)
+ private val fadeUI =
+ listOf<View>(
+ view.requireViewById(R.id.screenshot_preview_border),
+ view.requireViewById(R.id.actions_container_background),
+ view.requireViewById(R.id.screenshot_badge),
+ view.requireViewById(R.id.screenshot_dismiss_button),
+ view.requireViewById(R.id.screenshot_message_container),
+ )
fun getEntranceAnimation(
bounds: Rect,
@@ -96,15 +116,108 @@
}
entranceAnimation.play(fadeInAnimator).after(previewAnimator)
entranceAnimation.doOnStart {
+ viewModel.setIsAnimating(true)
for (child in staticUI) {
child.alpha = 0f
}
}
+ entranceAnimation.doOnEnd { viewModel.setIsAnimating(false) }
this.animator = entranceAnimation
return entranceAnimation
}
+ fun fadeForSharedTransition() {
+ animator?.cancel()
+ val fadeAnimator = ValueAnimator.ofFloat(1f, 0f)
+ fadeAnimator.addUpdateListener {
+ for (view in fadeUI) {
+ view.alpha = it.animatedValue as Float
+ }
+ }
+ animator = fadeAnimator
+ fadeAnimator.start()
+ }
+
+ fun runLongScreenshotTransition(
+ destRect: Rect,
+ longScreenshot: ScrollCaptureController.LongScreenshot,
+ onTransitionEnd: Runnable
+ ): Animator {
+ val animSet = AnimatorSet()
+
+ val scrimAnim = ValueAnimator.ofFloat(0f, 1f)
+ scrimAnim.addUpdateListener { animation: ValueAnimator ->
+ scrollingScrim.setAlpha(1 - animation.animatedFraction)
+ }
+ scrollTransitionPreview.visibility = View.VISIBLE
+ if (true) {
+ scrollTransitionPreview.setImageBitmap(longScreenshot.toBitmap())
+ val startX: Float = scrollTransitionPreview.x
+ val startY: Float = scrollTransitionPreview.y
+ val locInScreen: IntArray = scrollTransitionPreview.getLocationOnScreen()
+ destRect.offset(startX.toInt() - locInScreen[0], startY.toInt() - locInScreen[1])
+ scrollTransitionPreview.pivotX = 0f
+ scrollTransitionPreview.pivotY = 0f
+ scrollTransitionPreview.setAlpha(1f)
+ val currentScale: Float = scrollTransitionPreview.width / longScreenshot.width.toFloat()
+ val matrix = Matrix()
+ matrix.setScale(currentScale, currentScale)
+ matrix.postTranslate(
+ longScreenshot.left * currentScale,
+ longScreenshot.top * currentScale
+ )
+ scrollTransitionPreview.setImageMatrix(matrix)
+ val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
+ val previewAnim = ValueAnimator.ofFloat(0f, 1f)
+ previewAnim.addUpdateListener { animation: ValueAnimator ->
+ val t = animation.animatedFraction
+ val currScale = MathUtils.lerp(1f, destinationScale, t)
+ scrollTransitionPreview.scaleX = currScale
+ scrollTransitionPreview.scaleY = currScale
+ scrollTransitionPreview.x = MathUtils.lerp(startX, destRect.left.toFloat(), t)
+ scrollTransitionPreview.y = MathUtils.lerp(startY, destRect.top.toFloat(), t)
+ }
+ val previewFadeAnim = ValueAnimator.ofFloat(1f, 0f)
+ previewFadeAnim.addUpdateListener { animation: ValueAnimator ->
+ scrollTransitionPreview.setAlpha(1 - animation.animatedFraction)
+ }
+ previewAnim.doOnEnd { onTransitionEnd.run() }
+ animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim)
+ } else {
+ // if we switched orientations between the original screenshot and the long screenshot
+ // capture, just fade out the scrim instead of running the preview animation
+ scrimAnim.doOnEnd { onTransitionEnd.run() }
+ animSet.play(scrimAnim)
+ }
+ animator = animSet
+ return animSet
+ }
+
+ fun fadeForLongScreenshotTransition() {
+ scrollingScrim.imageTintBlendMode = BlendMode.SRC_ATOP
+ val anim = ValueAnimator.ofFloat(0f, .3f)
+ anim.addUpdateListener {
+ scrollingScrim.setImageTintList(
+ ColorStateList.valueOf(Color.argb(it.animatedValue as Float, 0f, 0f, 0f))
+ )
+ }
+ for (view in fadeUI) {
+ view.alpha = 0f
+ }
+ screenshotPreview.alpha = 0f
+ anim.setDuration(200)
+ anim.start()
+ }
+
+ fun restoreUI() {
+ animator?.cancel()
+ for (view in fadeUI) {
+ view.alpha = 1f
+ }
+ screenshotPreview.alpha = 1f
+ }
+
fun getSwipeReturnAnimation(): Animator {
animator?.cancel()
val animator = ValueAnimator.ofFloat(view.translationX, 0f)
@@ -114,6 +227,7 @@
}
fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator {
+ animator?.cancel()
val velocity = getAdjustedVelocity(requestedVelocity)
val screenWidth = view.resources.displayMetrics.widthPixels
// translation at which point the visible UI is fully off the screen (in the direction
@@ -131,6 +245,8 @@
view.alpha = 1f - it.animatedFraction
}
animator.duration = ((abs(distance / velocity))).toLong()
+ animator.doOnStart { viewModel.setIsAnimating(true) }
+ animator.doOnEnd { viewModel.setIsAnimating(false) }
this.animator = animator
return animator
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index bd93226..969cf48 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -22,6 +22,8 @@
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -43,13 +45,43 @@
private val displayMetrics = context.resources.displayMetrics
private val tmpRect = Rect()
private lateinit var actionsContainerBackground: View
+ private lateinit var actionsContainer: View
private lateinit var dismissButton: View
+ // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception
+ // session (with the client's provided `onTouchInterceptListener`). We delegate out to their
+ // listener only for gestures that can't be handled by scrolling our `actionsContainer`.
+ private val gestureDetector =
+ GestureDetector(
+ context,
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ ev1: MotionEvent?,
+ ev2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ actionsContainer.getBoundsOnScreen(tmpRect)
+ val touchedInActionsContainer =
+ tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt())
+ val canHandleInternallyByScrolling =
+ touchedInActionsContainer
+ && actionsContainer.canScrollHorizontally(distanceX.toInt())
+ return !canHandleInternallyByScrolling
+ }
+ }
+ )
+
init {
- setOnTouchListener({ _: View, _: MotionEvent ->
+
+ // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated
+ // touch-interception.
+ setOnTouchListener({ _: View, ev: MotionEvent ->
userInteractionCallback?.invoke()
- true
+ onTouchInterceptListener?.invoke(ev) ?: false
})
+
+ gestureDetector.setIsLongpressEnabled(false)
}
override fun onFinishInflate() {
@@ -60,7 +92,15 @@
blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur)
screenshotStatic = requireViewById(R.id.screenshot_static)
actionsContainerBackground = requireViewById(R.id.actions_container_background)
+ actionsContainer = requireViewById(R.id.actions_container)
dismissButton = requireViewById(R.id.screenshot_dismiss_button)
+
+ // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already
+ // being handled by our child views.
+ actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent ->
+ userInteractionCallback?.invoke()
+ false
+ })
}
fun getTouchRegion(gestureInsets: Insets): Region {
@@ -171,9 +211,16 @@
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
userInteractionCallback?.invoke()
- if (onTouchInterceptListener?.invoke(ev) == true) {
- return true
+ // Let the client-provided listener see all `DOWN` events so that they'll be able to
+ // interpret the remainder of the gesture, even if interception starts partway-through.
+ // TODO: is this really necessary? And if we don't go on to start interception, should we
+ // follow up with `ACTION_CANCEL`?
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ onTouchInterceptListener?.invoke(ev)
}
- return super.onInterceptTouchEvent(ev)
+
+ // Only allow the client-provided touch interceptor to take over the gesture if our
+ // top-level `GestureDetector` decides not to scroll the action container.
+ return gestureDetector.onTouchEvent(ev)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index 2243ade..36aa39f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -23,8 +23,9 @@
import com.android.systemui.res.R
import com.android.systemui.screenshot.ui.TransitioningIconDrawable
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import javax.inject.Inject
-object ActionButtonViewBinder {
+class ActionButtonViewBinder @Inject constructor() {
/** Binds the given view to the given view-model */
fun bind(view: View, viewModel: ActionButtonViewModel) {
val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon)
@@ -36,6 +37,10 @@
// Note we never re-bind a view to a different ActionButtonViewModel, different view
// models would remove/create separate views.
drawable?.setIcon(viewModel.appearance.icon)
+ iconView.setImageDrawable(viewModel.appearance.icon)
+ if (!viewModel.appearance.tint) {
+ iconView.setImageTintList(null)
+ }
textView.text = viewModel.appearance.label
viewModel.appearance.customBackground?.also {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 89f904a..442b387 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,7 +16,11 @@
package com.android.systemui.screenshot.ui.binder
+import android.content.res.Configuration
import android.graphics.Bitmap
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.util.LayoutDirection
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -29,22 +33,26 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent
+import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.SwipeGestureListener
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.AnimationState
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.children
+import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-object ScreenshotShelfViewBinder {
+class ScreenshotShelfViewBinder
+@Inject
+constructor(private val buttonViewBinder: ActionButtonViewBinder) {
fun bind(
view: ScreenshotShelfView,
viewModel: ScreenshotViewModel,
+ animationController: ScreenshotAnimationController,
layoutInflater: LayoutInflater,
onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit,
- onDismissalCancelled: () -> Unit,
onUserInteraction: () -> Unit
) {
val swipeGestureListener =
@@ -53,7 +61,7 @@
onDismiss = {
onDismissalRequested(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it)
},
- onCancel = onDismissalCancelled
+ onCancel = { animationController.getSwipeReturnAnimation().start() }
)
view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) }
view.userInteractionCallback = onUserInteraction
@@ -63,11 +71,15 @@
val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border)
previewView.clipToOutline = true
previewViewBlur.clipToOutline = true
+ val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button)
dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE
dismissButton.setOnClickListener {
onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null)
}
+ val scrollingScrim: ImageView = view.requireViewById(R.id.screenshot_scrolling_scrim)
+ val scrollablePreview: ImageView = view.requireViewById(R.id.screenshot_scrollable_preview)
+ val badgeView = view.requireViewById<ImageView>(R.id.screenshot_badge)
// use immediate dispatcher to ensure screenshot bitmap is set before animation
view.repeatWhenAttached(Dispatchers.Main.immediate) {
@@ -87,11 +99,48 @@
}
}
launch {
+ viewModel.scrollingScrim.collect { bitmap ->
+ if (bitmap != null) {
+ scrollingScrim.setImageBitmap(bitmap)
+ scrollingScrim.visibility = View.VISIBLE
+ } else {
+ scrollingScrim.visibility = View.GONE
+ }
+ }
+ }
+ launch {
+ viewModel.scrollableRect.collect { rect ->
+ if (rect != null) {
+ setScrollablePreview(
+ scrollablePreview,
+ viewModel.preview.value,
+ rect
+ )
+ } else {
+ scrollablePreview.visibility = View.GONE
+ }
+ }
+ }
+ launch {
+ viewModel.badge.collect { badge ->
+ badgeView.setImageDrawable(badge)
+ badgeView.visibility = if (badge != null) View.VISIBLE else View.GONE
+ }
+ }
+ launch {
viewModel.previewAction.collect { onClick ->
previewView.setOnClickListener { onClick?.invoke() }
}
}
launch {
+ viewModel.isAnimating.collect { isAnimating ->
+ previewView.isClickable = !isAnimating
+ for (child in actionsContainer.children) {
+ child.isClickable = !isAnimating
+ }
+ }
+ }
+ launch {
viewModel.actions.collect { actions ->
updateActions(
actions,
@@ -151,14 +200,14 @@
val currentView: View? = actionsContainer.getChildAt(index)
if (action.id == currentView?.tag) {
// Same ID, update the display
- ActionButtonViewBinder.bind(currentView, action)
+ buttonViewBinder.bind(currentView, action)
} else {
// Different ID. Removals have already happened so this must
// mean that the new action must be inserted here.
val actionButton =
layoutInflater.inflate(R.layout.shelf_action_chip, actionsContainer, false)
actionsContainer.addView(actionButton, index)
- ActionButtonViewBinder.bind(actionButton, action)
+ buttonViewBinder.bind(actionButton, action)
}
}
}
@@ -181,4 +230,35 @@
screenshotPreview.layoutParams = params
screenshotPreview.requestLayout()
}
+
+ private fun setScrollablePreview(
+ scrollablePreview: ImageView,
+ bitmap: Bitmap?,
+ scrollableRect: Rect
+ ) {
+ if (bitmap == null) {
+ return
+ }
+ val fixedSize = scrollablePreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ val inPortrait =
+ scrollablePreview.resources.configuration.orientation ==
+ Configuration.ORIENTATION_PORTRAIT
+ val scale: Float = fixedSize / ((if (inPortrait) bitmap.width else bitmap.height).toFloat())
+ val params = scrollablePreview.layoutParams
+
+ params.width = (scale * scrollableRect.width()).toInt()
+ params.height = (scale * scrollableRect.height()).toInt()
+ val matrix = Matrix()
+ matrix.setScale(scale, scale)
+ matrix.postTranslate(-scrollableRect.left * scale, -scrollableRect.top * scale)
+
+ scrollablePreview.translationX =
+ (scale *
+ if (scrollablePreview.layoutDirection == LayoutDirection.LTR) scrollableRect.left
+ else scrollableRect.right - (scrollablePreview.parent as View).width)
+ scrollablePreview.translationY = scale * scrollableRect.top
+ scrollablePreview.setImageMatrix(matrix)
+ scrollablePreview.setImageBitmap(bitmap)
+ scrollablePreview.setVisibility(View.VISIBLE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index 2982ea0..42ad326 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -25,5 +25,6 @@
val icon: Drawable?,
val label: CharSequence?,
val description: CharSequence,
+ val tint: Boolean = true,
val customBackground: Drawable? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index 5f36f73..3f99bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.screenshot.ui.viewmodel
import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
import android.util.Log
import android.view.accessibility.AccessibilityManager
import kotlinx.coroutines.flow.MutableStateFlow
@@ -25,6 +27,10 @@
class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
private val _preview = MutableStateFlow<Bitmap?>(null)
val preview: StateFlow<Bitmap?> = _preview
+ private val _scrollingScrim = MutableStateFlow<Bitmap?>(null)
+ val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim
+ private val _badge = MutableStateFlow<Drawable?>(null)
+ val badge: StateFlow<Drawable?> = _badge
private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
val previewAction: StateFlow<(() -> Unit)?> = _previewAction
private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
@@ -32,6 +38,10 @@
private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
val animationState: StateFlow<AnimationState> = _animationState
+ private val _isAnimating = MutableStateFlow(false)
+ val isAnimating: StateFlow<Boolean> = _isAnimating
+ private val _scrollableRect = MutableStateFlow<Rect?>(null)
+ val scrollableRect: StateFlow<Rect?> = _scrollableRect
val showDismissButton: Boolean
get() = accessibilityManager.isEnabled
@@ -39,6 +49,14 @@
_preview.value = bitmap
}
+ fun setScrollingScrimBitmap(bitmap: Bitmap?) {
+ _scrollingScrim.value = bitmap
+ }
+
+ fun setScreenshotBadge(badge: Drawable?) {
+ _badge.value = badge
+ }
+
fun setPreviewAction(onClick: () -> Unit) {
_previewAction.value = onClick
}
@@ -107,11 +125,23 @@
_animationState.value = state
}
+ fun setIsAnimating(isAnimating: Boolean) {
+ _isAnimating.value = isAnimating
+ }
+
+ fun setScrollableRect(rect: Rect?) {
+ _scrollableRect.value = rect
+ }
+
fun reset() {
_preview.value = null
+ _scrollingScrim.value = null
+ _badge.value = null
_previewAction.value = null
_actions.value = listOf()
_animationState.value = AnimationState.NOT_STARTED
+ _isAnimating.value = false
+ _scrollableRect.value = null
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index 288ff09..84156eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -51,6 +51,16 @@
return super.onTouchEvent(event);
}
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ setHovered(true);
+ } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ setHovered(false);
+ }
+ return true;
+ }
+
public void setAccessibilityLabel(String label) {
mAccessibilityLabel = label;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ee7b4be..22aa492 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -123,15 +123,9 @@
private var anyBouncerShowing = false
/**
- * True if the shade is fully expanded and the user is not interacting with it anymore, meaning
- * the hub should not receive any touch input.
+ * True if the shade is fully expanded, meaning the hub should not receive any touch input.
*
- * We need to not pause the touch handling lifecycle as soon as the shade opens because if the
- * user swipes down, then back up without lifting their finger, the lifecycle will be paused
- * then resumed, and resuming force-stops all active touch sessions. This means the shade will
- * not receive the end of the gesture and will be stuck open.
- *
- * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
+ * Tracks [ShadeInteractor.isAnyFullyExpanded].
*/
private var shadeShowing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7051d5f..6bb30c7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -141,6 +141,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
import com.android.systemui.keyguard.shared.ComposeLockscreen;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
@@ -170,6 +171,7 @@
import com.android.systemui.power.shared.model.WakefulnessModel;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -1130,8 +1132,12 @@
controller.setup(mNotificationContainerParent));
// Dreaming->Lockscreen
- collectFlow(mView, mKeyguardTransitionInteractor.transition(DREAMING, LOCKSCREEN),
- mDreamingToLockscreenTransition, mMainDispatcher);
+ collectFlow(
+ mView,
+ mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(DREAMING, LOCKSCREEN)),
+ mDreamingToLockscreenTransition,
+ mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
mMainDispatcher);
@@ -1141,7 +1147,8 @@
// Gone -> Dreaming hosted in lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .transition(GONE, DREAMING_LOCKSCREEN_HOSTED),
+ .transition(Edge.Companion.create(Scenes.Gone, DREAMING_LOCKSCREEN_HOSTED),
+ Edge.Companion.create(GONE, DREAMING_LOCKSCREEN_HOSTED)),
mGoneToDreamingLockscreenHostedTransition, mMainDispatcher);
collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController),
@@ -1149,16 +1156,17 @@
// Lockscreen -> Dreaming hosted in lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED),
+ .transition(Edge.Companion.create(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)),
mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher);
// Dreaming hosted in lockscreen -> Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN),
+ .transition(Edge.Companion.create(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)),
mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher);
// Occluded->Lockscreen
- collectFlow(mView, mKeyguardTransitionInteractor.transition(OCCLUDED, LOCKSCREEN),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(OCCLUDED, LOCKSCREEN)),
mOccludedToLockscreenTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
@@ -1169,7 +1177,8 @@
}
// Lockscreen->Dreaming
- collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(LOCKSCREEN, DREAMING)),
mLockscreenToDreamingTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1181,7 +1190,9 @@
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
// Gone->Dreaming
- collectFlow(mView, mKeyguardTransitionInteractor.transition(GONE, DREAMING),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(Scenes.Gone, DREAMING),
+ Edge.Companion.create(GONE, DREAMING)),
mGoneToDreamingTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1192,7 +1203,8 @@
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
// Lockscreen->Occluded
- collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, OCCLUDED),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(LOCKSCREEN, OCCLUDED)),
mLockscreenToOccludedTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index b50a3cd..6efa633 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.res.R;
@@ -137,11 +138,6 @@
private final PanelExpansionInteractor mPanelExpansionInteractor;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
- /**
- * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been
- * intercepted and all future touch events for the gesture should be processed by this view.
- */
- private boolean mExternalTouchIntercepted = false;
private boolean mIsTrackingBarGesture = false;
private boolean mIsOcclusionTransitionRunning = false;
private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener;
@@ -225,7 +221,8 @@
mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
- collectFlow(mView, keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
+ collectFlow(mView, keyguardTransitionInteractor.transition(
+ Edge.Companion.create(LOCKSCREEN, DREAMING)),
mLockscreenToDreamingTransition);
collectFlow(
mView,
@@ -258,28 +255,11 @@
}
/**
- * Handle a touch event while dreaming or on the hub by forwarding the event to the content
- * view.
- * <p>
- * Since important logic for handling touches lives in the dispatch/intercept phases, we
- * simulate going through all of these stages before sending onTouchEvent if intercepted.
- *
+ * Handle a touch event while dreaming by forwarding the event to the content view.
* @param event The event to forward.
*/
- public void handleExternalTouch(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mExternalTouchIntercepted = false;
- }
-
- if (!mView.dispatchTouchEvent(event)) {
- return;
- }
- if (!mExternalTouchIntercepted) {
- mExternalTouchIntercepted = mView.onInterceptTouchEvent(event);
- }
- if (mExternalTouchIntercepted) {
- mView.onTouchEvent(event);
- }
+ public void handleDreamTouch(MotionEvent event) {
+ mView.dispatchTouchEvent(event);
}
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index d2c93da..884ccef 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -140,7 +140,7 @@
private fun animateCollapseShadeInternal() {
sceneInteractor.changeScene(
- getCollapseDestinationScene(),
+ getCollapseDestinationScene(), // TODO(b/336581871): add sceneState?
"ShadeController.animateCollapseShade",
SlightlyFasterShadeCollapse,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index c9949cd..55bd8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -44,6 +44,7 @@
} else {
Scenes.Shade
}
+ // TODO(b/336581871): add sceneState?
sceneInteractor.changeScene(key, "animateCollapseQs")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d955349..c912616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -134,7 +134,7 @@
private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT;
private static final int MSG_HANDLE_SYSTEM_KEY = 33 << MSG_SHIFT;
private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT;
- private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_NOTIFICATION_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
@@ -180,6 +180,7 @@
private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -222,10 +223,33 @@
*/
default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
boolean animate) { }
+
+ /**
+ * Called to expand Notifications panel with animation.
+ */
default void animateExpandNotificationsPanel() { }
+ /**
+ * Called to collapse Notifications panel with animation.
+ * @param flags Exclusion flags. See {@link FLAG_EXCLUDE_NONE}.
+ * @param force True to force the operation.
+ */
default void animateCollapsePanels(int flags, boolean force) { }
- default void togglePanel() { }
- default void animateExpandSettingsPanel(String obj) { }
+
+ /**
+ * Called to toggle Notifications panel.
+ */
+ default void toggleNotificationsPanel() { }
+
+ /**
+ * Called to expand Quick Settings panel with animation.
+ * @param subPanel subPanel one wish to expand.
+ */
+ default void animateExpandSettingsPanel(String subPanel) { }
+
+ /**
+ * Called to toggle Quick Settings panel.
+ */
+ default void toggleQuickSettingsPanel() { }
/**
* Called to notify IME window status changes.
@@ -696,10 +720,10 @@
}
}
- public void togglePanel() {
+ public void toggleNotificationsPanel() {
synchronized (mLock) {
- mHandler.removeMessages(MSG_TOGGLE_PANEL);
- mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget();
+ mHandler.removeMessages(MSG_TOGGLE_NOTIFICATION_PANEL);
+ mHandler.obtainMessage(MSG_TOGGLE_NOTIFICATION_PANEL, 0, 0).sendToTarget();
}
}
@@ -710,6 +734,13 @@
}
}
+ public void toggleQuickSettingsPanel() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_TOGGLE_QUICK_SETTINGS_PANEL);
+ mHandler.obtainMessage(MSG_TOGGLE_QUICK_SETTINGS_PANEL, 0, 0).sendToTarget();
+ }
+ }
+
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
@@ -1494,9 +1525,9 @@
mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0);
}
break;
- case MSG_TOGGLE_PANEL:
+ case MSG_TOGGLE_NOTIFICATION_PANEL:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).togglePanel();
+ mCallbacks.get(i).toggleNotificationsPanel();
}
break;
case MSG_EXPAND_SETTINGS:
@@ -1504,6 +1535,11 @@
mCallbacks.get(i).animateExpandSettingsPanel((String) msg.obj);
}
break;
+ case MSG_TOGGLE_QUICK_SETTINGS_PANEL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).toggleQuickSettingsPanel();
+ }
+ break;
case MSG_SHOW_IME_BUTTON:
args = (SomeArgs) msg.obj;
handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d7d3732..5bf2f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar;
+import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -175,14 +177,18 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
+ if (mediaControlsUserInitiatedDismiss() && !userInitiated) {
+ // Dismissing the notification will send the app's deleteIntent, so ignore if
+ // this was an automatic removal
+ Log.d(TAG, "Not dismissing " + key + " because it was removed by the system");
+ return;
+ }
mNotifPipeline.getAllNotifs()
.stream()
.filter(entry -> Objects.equals(entry.getKey(), key))
.findAny()
.ifPresent(entry -> {
- // TODO(b/160713608): "removing" this notification won't happen and
- // won't send the 'deleteIntent' if the notification is ongoing.
mNotifCollection.dismissNotification(entry,
getDismissedByUserStats(entry));
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
index ce88a5f..cae86a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.content.Context
import android.util.AttributeSet
import com.android.systemui.animation.view.LaunchableLinearLayout
/**
- * A container view for the ongoing call chip background. Needed so that we can limit the height of
- * the background when the font size is very large (200%), in which case the background would go
+ * A container view for the ongoing activity chip background. Needed so that we can limit the height
+ * of the background when the font size is very large (200%), in which case the background would go
* past the bounds of the status bar.
*/
-class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) :
+class ChipBackgroundContainer(context: Context, attrs: AttributeSet) :
LaunchableLinearLayout(context, attrs) {
/** Sets where this view should fetch its max height from. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index bb7ba4c..ff3061e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -14,36 +14,34 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.content.Context
import android.util.AttributeSet
-
import android.widget.Chronometer
import androidx.annotation.UiThread
/**
- * A [Chronometer] specifically for the ongoing call chip in the status bar.
+ * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an
+ * activity.
*
* This class handles:
- * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would
- * change slightly each second because the width of each number is slightly different.
+ * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change
+ * slightly each second because the width of each number is slightly different.
*
- * Instead, we save the largest number width seen so far and ensure that the chip is at least
- * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59
- * to 1:00:00), but never smaller.
- *
- * 2) Hiding the text if the time gets too long for the space available. Once the text has been
- * hidden, it remains hidden for the duration of the call.
+ * Instead, we save the largest number width seen so far and ensure that the chip is at least
+ * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
+ * 1:00:00), but never smaller.
+ * 2) Hiding the text if the time gets too long for the space available. Once the text has been
+ * hidden, it remains hidden for the duration of the activity.
*
* Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
* text will also be hidden in landscape (even if there is enough space for it in landscape).
*/
-class OngoingCallChronometer @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
-) : Chronometer(context, attrs, defStyle) {
+class ChipChronometer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+ Chronometer(context, attrs, defStyle) {
// Minimum width that the text view can be. Corresponds with the largest number width seen so
// far.
@@ -53,8 +51,8 @@
private var shouldHideText: Boolean = false
override fun setBase(base: Long) {
- // These variables may have changed during the previous call, so re-set them before the new
- // call starts.
+ // These variables may have changed during the previous activity, so re-set them before the
+ // new activity starts.
minimumTextWidth = 0
shouldHideText = false
visibility = VISIBLE
@@ -75,9 +73,7 @@
}
// Evaluate how wide the text *wants* to be if it had unlimited space.
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- heightMeasureSpec)
+ super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
val desiredTextWidth = measuredWidth
// Evaluate how wide the text *can* be based on the enforced constraints
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 3d0fd89..af2c197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -31,8 +31,10 @@
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE
import javax.inject.Inject
/**
@@ -81,6 +83,13 @@
}
}
+ val priorityPeopleSectioner =
+ object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) {
+ override fun isInSection(entry: ListEntry): Boolean {
+ return getPeopleType(entry) == TYPE_IMPORTANT_PERSON
+ }
+ }
+
// TODO(b/330193582): Rename to just "People"
val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
override fun isInSection(entry: ListEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 1a223c1..42bf4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -18,12 +18,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import android.os.SystemProperties
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
-import com.android.systemui.Flags.notificationMinimalismPrototype
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -33,14 +31,20 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -107,6 +111,10 @@
}
private fun attachUnseenFilter(pipeline: NotifPipeline) {
+ if (NotificationMinimalismPrototype.V2.isEnabled) {
+ pipeline.addPromoter(unseenNotifPromoter)
+ pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotif)
+ }
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
scope.launch { trackUnseenFilterSettingChanges() }
@@ -264,7 +272,10 @@
}
private fun unseenFeatureEnabled(): Flow<Boolean> {
- if (notificationMinimalismPrototype()) {
+ if (
+ NotificationMinimalismPrototype.V1.isEnabled ||
+ NotificationMinimalismPrototype.V2.isEnabled
+ ) {
return flowOf(true)
}
return secureSettings
@@ -335,6 +346,57 @@
}
}
+ private fun pickOutTopUnseenNotif(list: List<ListEntry>) {
+ if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+ // Only ever elevate a top unseen notification on keyguard, not even locked shade
+ if (statusBarStateController.state != StatusBarState.KEYGUARD) {
+ seenNotificationsInteractor.setTopUnseenNotification(null)
+ return
+ }
+ // On keyguard pick the top-ranked unseen or ongoing notification to elevate
+ seenNotificationsInteractor.setTopUnseenNotification(
+ list
+ .asSequence()
+ .flatMap {
+ when (it) {
+ is NotificationEntry -> listOfNotNull(it)
+ is GroupEntry -> it.children
+ else -> error("unhandled type of $it")
+ }
+ }
+ .filter { shouldIgnoreUnseenCheck(it) || it in unseenNotifications }
+ .minByOrNull { it.ranking.rank }
+ )
+ }
+
+ @VisibleForTesting
+ internal val unseenNotifPromoter =
+ object : NotifPromoter("$TAG-unseen") {
+ override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
+ if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+ else
+ seenNotificationsInteractor.isTopUnseenNotification(child) &&
+ NotificationMinimalismPrototype.V2.ungroupTopUnseen
+ }
+
+ val unseenNotifSectioner =
+ object : NotifSectioner("Unseen", BUCKET_FOREGROUND_SERVICE) {
+ override fun isInSection(entry: ListEntry): Boolean {
+ if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
+ if (
+ seenNotificationsInteractor.isTopUnseenNotification(entry.representativeEntry)
+ ) {
+ return true
+ }
+ if (entry !is GroupEntry) {
+ return false
+ }
+ return entry.children.any {
+ seenNotificationsInteractor.isTopUnseenNotification(it)
+ }
+ }
+ }
+
@VisibleForTesting
internal val unseenNotifFilter =
object : NotifFilter("$TAG-unseen") {
@@ -342,18 +404,6 @@
var hasFilteredAnyNotifs = false
/**
- * the [notificationMinimalismPrototype] will now show seen notifications on the locked
- * shade by default, but this property read allows that to be quickly disabled for
- * testing
- */
- private val minimalismShowOnLockedShade
- get() =
- SystemProperties.getBoolean(
- "persist.notification_minimalism_prototype.show_on_locked_shade",
- true
- )
-
- /**
* Encapsulates a definition of "being on the keyguard". Note that these two definitions
* are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
* not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
@@ -364,7 +414,12 @@
* allow seen notifications to appear in the locked shade.
*/
private fun isOnKeyguard(): Boolean =
- if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) {
+ if (NotificationMinimalismPrototype.V2.isEnabled) {
+ false // disable this feature under this prototype
+ } else if (
+ NotificationMinimalismPrototype.V1.isEnabled &&
+ NotificationMinimalismPrototype.V1.showOnLockedShade
+ ) {
statusBarStateController.state == StatusBarState.KEYGUARD
} else {
keyguardRepository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 36c12a7..4506385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -24,47 +24,51 @@
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
/**
- * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
- * Coordinators can register their respective callbacks.
+ * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the Coordinators can
+ * register their respective callbacks.
*/
interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
-class NotifCoordinatorsImpl @Inject constructor(
- sectionStyleProvider: SectionStyleProvider,
- featureFlags: FeatureFlags,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- colorizedFgsCoordinator: ColorizedFgsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- groupWhenCoordinator: GroupWhenCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAlertTimeCoordinator: RowAlertTimeCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator,
- dreamCoordinator: DreamCoordinator,
- statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
+class NotifCoordinatorsImpl
+@Inject
+constructor(
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ colorizedFgsCoordinator: ColorizedFgsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAlertTimeCoordinator: RowAlertTimeCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator,
+ dreamCoordinator: DreamCoordinator,
+ statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
) : NotifCoordinators {
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -114,6 +118,12 @@
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
+ if (NotificationMinimalismPrototype.V2.isEnabled) {
+ mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS)
+ }
+ if (PriorityPeopleSection.isEnabled) {
+ mOrderedSections.add(conversationCoordinator.priorityPeopleSectioner) // Priority People
+ }
mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
if (!SortBySectionTimeFlag.isEnabled) {
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
@@ -124,22 +134,26 @@
sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
if (SortBySectionTimeFlag.isEnabled) {
- sectionStyleProvider.setSilentSections(listOf(
+ sectionStyleProvider.setSilentSections(
+ listOf(
rankingCoordinator.silentSectioner,
rankingCoordinator.minimizedSectioner,
- ))
+ )
+ )
} else {
- sectionStyleProvider.setSilentSections(listOf(
+ sectionStyleProvider.setSilentSections(
+ listOf(
conversationCoordinator.peopleSilentSectioner,
rankingCoordinator.silentSectioner,
rankingCoordinator.minimizedSectioner,
- ))
+ )
+ )
}
}
/**
- * Sends the pipeline to each coordinator when the pipeline is ready to accept
- * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s.
+ * Sends the pipeline to each coordinator when the pipeline is ready to accept [Pluggable]s,
+ * [NotifCollectionListener]s and [NotifLifetimeExtender]s.
*/
override fun attach(pipeline: NotifPipeline) {
for (c in mCoreCoordinators) {
@@ -155,10 +169,11 @@
* As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted
* as they are dumped in the RenderStageManager instead.
*/
- override fun dumpPipeline(d: PipelineDumper) = with(d) {
- dump("core coordinators", mCoreCoordinators)
- dump("normal coordinators", mCoordinators)
- }
+ override fun dumpPipeline(d: PipelineDumper) =
+ with(d) {
+ dump("core coordinators", mCoreCoordinators)
+ dump("normal coordinators", mCoordinators)
+ }
companion object {
private const val TAG = "NotifCoordinators"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 350e88e..caa6c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -38,6 +38,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.Compile;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -63,6 +65,7 @@
public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final ShadeAnimationInteractor mShadeAnimationInteractor;
private final StatusBarStateController mStatusBarStateController;
private final JavaAdapter mJavaAdapter;
@@ -101,6 +104,7 @@
HeadsUpManager headsUpManager,
ShadeAnimationInteractor shadeAnimationInteractor,
JavaAdapter javaAdapter,
+ SeenNotificationsInteractor seenNotificationsInteractor,
StatusBarStateController statusBarStateController,
VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
@@ -109,6 +113,7 @@
mHeadsUpManager = headsUpManager;
mShadeAnimationInteractor = shadeAnimationInteractor;
mJavaAdapter = javaAdapter;
+ mSeenNotificationsInteractor = seenNotificationsInteractor;
mVisibilityLocationProvider = visibilityLocationProvider;
mVisualStabilityProvider = visualStabilityProvider;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -142,8 +147,15 @@
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
private boolean canMoveForHeadsUp(NotificationEntry entry) {
- return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
- && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ if (entry == null) {
+ return false;
+ }
+ boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled()
+ && mSeenNotificationsInteractor.isTopUnseenNotification(entry);
+ if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
+ return !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ }
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 5c844bc..e2c9e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -41,6 +41,9 @@
/** Stats about the list of notifications attached to the shade */
val notifStats = MutableStateFlow(NotifStats.empty)
+
+ /** The key of the top unseen notification */
+ val topUnseenNotificationKey = MutableStateFlow<String?>(null)
}
/** Represents the notification list, comprised of groups and individual notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 1f7ab96..42828d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@@ -36,4 +38,15 @@
fun setHasFilteredOutSeenNotifications(value: Boolean) {
notificationListRepository.hasFilteredOutSeenNotifications.value = value
}
+
+ /** Set the entry that is identified as the top unseen notification. */
+ fun setTopUnseenNotification(entry: NotificationEntry?) {
+ if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+ notificationListRepository.topUnseenNotificationKey.value = entry?.key
+ }
+
+ /** Determine if the given notification is the top unseen notification. */
+ fun isTopUnseenNotification(entry: NotificationEntry?): Boolean =
+ if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+ else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 968b591..5a616df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,7 +18,7 @@
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
-import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.annotation.ColorInt;
@@ -407,7 +407,7 @@
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final @ColorInt int scHigh;
- if (!notificationBackgroundTintOptimization()) {
+ if (!notificationFooterBackgroundTintOptimization()) {
scHigh = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorSurfaceContainerHigh);
if (scHigh != 0) {
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 bfc5932..a901c5f 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
@@ -241,9 +241,6 @@
) {
val TAG = "AvalancheSuppressor"
- override var reason: String = "avalanche"
- protected set
-
enum class State {
ALLOW_CONVERSATION_AFTER_AVALANCHE,
ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -257,19 +254,15 @@
override fun shouldSuppress(entry: NotificationEntry): Boolean {
if (!isCooldownEnabled()) {
- reason = "FALSE avalanche cooldown setting DISABLED"
return false
}
val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
if (timedOut) {
- reason = "FALSE avalanche event TIMED OUT. " +
- "${timeSinceAvalancheMs/1000} seconds since last avalanche"
return false
}
val state = calculateState(entry)
if (state != State.SUPPRESS) {
- reason = "FALSE avalanche IN ALLOWLIST: $state"
return false
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 89aa3ab..9e0dd8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -21,6 +21,7 @@
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PRIORITY_PEOPLE;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import android.annotation.Nullable;
@@ -130,7 +131,8 @@
case BUCKET_HEADS_UP: return Notifications.Notification.SECTION_HEADS_UP;
case BUCKET_FOREGROUND_SERVICE:
return Notifications.Notification.SECTION_FOREGROUND_SERVICE;
- case BUCKET_PEOPLE: return Notifications.Notification.SECTION_PEOPLE;
+ case BUCKET_PEOPLE, BUCKET_PRIORITY_PEOPLE:
+ return Notifications.Notification.SECTION_PEOPLE;
case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING;
case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT;
}
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 747cb3c..edd2961 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
@@ -101,6 +101,7 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -590,7 +591,9 @@
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
- if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ mChildrenContainer.updateGroupHeaderExpandState();
+ } else {
// We create the header from the background thread instead
mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
isConversation());
@@ -843,6 +846,16 @@
}
/**
+ *
+ * @return true when compact version of Heads Up is on the screen.
+ */
+ public boolean isCompactConversationHeadsUpOnScreen() {
+ final NotificationViewWrapper viewWrapper =
+ getVisibleNotificationViewWrapper();
+
+ return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper;
+ }
+ /**
* @see NotificationChildrenContainer#setUntruncatedChildCount(int)
*/
public void setUntruncatedChildCount(int childCount) {
@@ -2788,7 +2801,10 @@
}
}
- protected void expandNotification() {
+ /**
+ * Triggers expand click listener to expand the notification.
+ */
+ public void expandNotification() {
mExpandClickListener.onClick(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index db3cf5a..9d0fcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Flags
+import android.os.SystemProperties
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import javax.inject.Inject
@@ -34,8 +35,13 @@
HeadsUpStyleProvider {
override fun shouldApplyCompactStyle(): Boolean {
- // Use compact HUN for immersive mode.
- return Flags.compactHeadsUpNotification() &&
- statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+ return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow())
}
+
+ private fun isInImmersiveMode() =
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+
+ /** developer setting to always show Minimal HUN, even if the device is not in full screen */
+ private fun alwaysShow() =
+ SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
index ce87d2f..3a5f3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
@@ -24,7 +24,7 @@
/**
* Compact Heads up Notifications template that doesn't set feedback icon and audibly alert icons
*/
-class NotificationCompactHeadsUpTemplateViewWrapper(
+open class NotificationCompactHeadsUpTemplateViewWrapper(
ctx: Context,
view: View,
row: ExpandableNotificationRow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt
new file mode 100644
index 0000000..bb40b56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/** Wraps a notification containing a messaging or conversation template */
+class NotificationCompactMessagingTemplateViewWrapper
+constructor(ctx: Context, view: View, row: ExpandableNotificationRow) :
+ NotificationCompactHeadsUpTemplateViewWrapper(ctx, view, row) {
+
+ private val compactMessagingView: ViewGroup = requireNotNull(view as? ViewGroup)
+
+ private var conversationIconView: CachingIconView? = null
+ private var expandBtn: View? = null
+ override fun onContentUpdated(row: ExpandableNotificationRow?) {
+ resolveViews()
+ super.onContentUpdated(row)
+ }
+
+ private fun resolveViews() {
+ conversationIconView = compactMessagingView.requireViewById(R.id.conversation_icon)
+ expandBtn = compactMessagingView.requireViewById(R.id.expand_button)
+ }
+
+ override fun updateTransformedTypes() {
+ super.updateTransformedTypes()
+
+ addViewsTransformingToSimilar(
+ conversationIconView,
+ expandBtn,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 4244542..22b95ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@
return new NotificationCallTemplateViewWrapper(ctx, v, row);
} else if ("compactHUN".equals((v.getTag()))) {
return new NotificationCompactHeadsUpTemplateViewWrapper(ctx, v, row);
+ } else if ("compactMessagingHUN".equals((v.getTag()))) {
+ return new NotificationCompactMessagingTemplateViewWrapper(ctx, v, row);
}
if (row.getEntry().getSbn().getNotification().isStyle(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
new file mode 100644
index 0000000..bf37036
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.notification.shared
+
+import android.os.SystemProperties
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the minimalism prototype flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationMinimalismPrototype {
+
+ val version: Int by lazy {
+ SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2)
+ }
+
+ object V1 {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationMinimalismPrototype() && version == 1
+
+ /**
+ * the prototype will now show seen notifications on the locked shade by default, but this
+ * property read allows that to be quickly disabled for testing
+ */
+ val showOnLockedShade: Boolean
+ get() =
+ if (isUnexpectedlyInLegacyMode()) false
+ else
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.show_on_locked_shade",
+ true
+ )
+
+ /** gets the configurable max number of notifications */
+ val maxNotifs: Int
+ get() =
+ if (isUnexpectedlyInLegacyMode()) -1
+ else
+ SystemProperties.getInt(
+ "persist.notification_minimalism_prototype.lock_screen_max_notifs",
+ 1
+ )
+
+ /**
+ * 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)
+ }
+ object V2 {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationMinimalismPrototype() && version == 2
+
+ /**
+ * The prototype will (by default) use a promoter to ensure that the top unseen notification
+ * is not grouped, but this property read allows that behavior to be disabled.
+ */
+ val ungroupTopUnseen: Boolean
+ get() =
+ if (isUnexpectedlyInLegacyMode()) false
+ else
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.ungroup_top_unseen",
+ true
+ )
+
+ /**
+ * 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/statusbar/notification/shared/PriorityPeopleSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt
new file mode 100644
index 0000000..472fd95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for com.android.systemui.Flags.FLAG_PRIORITY_PEOPLE_SECTION */
+@Suppress("NOTHING_TO_INLINE")
+object PriorityPeopleSection {
+ const val FLAG_NAME = Flags.FLAG_PRIORITY_PEOPLE_SECTION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Are sections sorted by time? */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.priorityPeopleSection()
+
+ /**
+ * 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/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 92c597c..48796d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -439,6 +439,15 @@
Trace.endSection();
}
+ /**
+ * Update the expand state of the group header.
+ */
+ public void updateGroupHeaderExpandState() {
+ if (mGroupHeaderWrapper != null) {
+ mGroupHeaderWrapper.setExpanded(mChildrenExpanded);
+ }
+ }
+
private void removeGroupHeader() {
if (mGroupHeader == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
index 31f4857..fc28a99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
@@ -3,15 +3,22 @@
import android.annotation.IntDef
/**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
+ * For now, declare the available notification buckets (sections) here so that other presentation
+ * code can decide what to do based on an entry's buckets
*/
@Retention(AnnotationRetention.SOURCE)
@IntDef(
- prefix = ["BUCKET_"],
- value = [
- BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
- BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+ prefix = ["BUCKET_"],
+ value =
+ [
+ BUCKET_UNKNOWN,
+ BUCKET_MEDIA_CONTROLS,
+ BUCKET_HEADS_UP,
+ BUCKET_FOREGROUND_SERVICE,
+ BUCKET_PRIORITY_PEOPLE,
+ BUCKET_PEOPLE,
+ BUCKET_ALERTING,
+ BUCKET_SILENT
]
)
annotation class PriorityBucket
@@ -20,6 +27,7 @@
const val BUCKET_MEDIA_CONTROLS = 1
const val BUCKET_HEADS_UP = 2
const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PRIORITY_PEOPLE = 7
const val BUCKET_PEOPLE = 4
const val BUCKET_ALERTING = 5
const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index abbe7d7..17b54c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -674,7 +674,7 @@
void setOverExpansion(float margin) {
mAmbientState.setOverExpansion(margin);
if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) {
- setRoundingClippingYTranslation((int) margin);
+ setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0);
}
updateStackPosition();
requestChildrenUpdate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 5bd4c75..4b0b1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -17,11 +17,9 @@
package com.android.systemui.statusbar.notification.stack
import android.content.res.Resources
-import android.os.SystemProperties
import android.util.Log
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags.notificationMinimalismPrototype
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -31,6 +29,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Compile
import com.android.systemui.util.children
@@ -73,6 +72,9 @@
*/
private var maxNotificationsExcludesMedia = false
+ /** Whether we allow keyguard to show less important notifications above the shelf. */
+ private var limitLockScreenToImportant = false
+
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
@@ -86,6 +88,14 @@
updateResources()
}
+ private fun allowedByPolicy(stackHeight: StackHeight): Boolean =
+ if (limitLockScreenToImportant && stackHeight.includesLessImportantNotification) {
+ log { "\tallowedByPolicy = false" }
+ false
+ } else {
+ true
+ }
+
/**
* Returns whether notifications and (shelf if visible) can fit in total space available.
* [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
@@ -184,11 +194,12 @@
log { "\tGet maxNotifWithoutSavingSpace ---" }
val maxNotifWithoutSavingSpace =
stackHeightSequence.lastIndexWhile { heightResult ->
- canStackFitInSpace(
- heightResult,
- notifSpace = notifSpace,
- shelfSpace = shelfSpace
- ) == FitResult.FIT
+ allowedByPolicy(heightResult) &&
+ canStackFitInSpace(
+ heightResult,
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) == FitResult.FIT
}
// How many notifications we can show at heightWithoutLockscreenConstraints
@@ -213,11 +224,12 @@
saveSpaceOnLockscreen = true
maxNotifications =
stackHeightSequence.lastIndexWhile { heightResult ->
- canStackFitInSpace(
- heightResult,
- notifSpace = notifSpace,
- shelfSpace = shelfSpace
- ) != FitResult.NO_FIT
+ allowedByPolicy(heightResult) &&
+ canStackFitInSpace(
+ heightResult,
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) != FitResult.NO_FIT
}
log { "\t--- maxNotifications=$maxNotifications" }
}
@@ -319,7 +331,10 @@
// Float height of shelf (0 if shelf is not showing), and space before the shelf that
// changes during the lockscreen <=> full shade transition.
- val shelfHeightWithSpaceBefore: Float
+ val shelfHeightWithSpaceBefore: Float,
+
+ /** Whether this stack height includes less at least one important notification. */
+ val includesLessImportantNotification: Boolean
)
private fun computeHeightPerNotificationLimit(
@@ -332,12 +347,15 @@
var previous: ExpandableView? = null
val onLockscreen = onLockscreen()
+ var includesLessImportantNotification = false
+
// Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
yield(
StackHeight(
notifsHeight = 0f,
notifsHeightSavingSpace = 0f,
- shelfHeightWithSpaceBefore = shelfHeight
+ shelfHeightWithSpaceBefore = shelfHeight,
+ includesLessImportantNotification = includesLessImportantNotification,
)
)
@@ -363,6 +381,19 @@
spaceBeforeShelf + shelfHeight
}
+ if (limitLockScreenToImportant && !includesLessImportantNotification) {
+ val bucket = (currentNotification as? ExpandableNotificationRow)?.entry?.bucket
+ includesLessImportantNotification =
+ when (bucket) {
+ null,
+ BUCKET_MEDIA_CONTROLS,
+ BUCKET_HEADS_UP,
+ BUCKET_FOREGROUND_SERVICE,
+ BUCKET_PRIORITY_PEOPLE -> false
+ else -> true
+ }
+ }
+
log {
"\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
"notifsHeightSavingSpace=$notifsWithCollapsedHun" +
@@ -372,7 +403,8 @@
StackHeight(
notifsHeight = notifications,
notifsHeightSavingSpace = notifsWithCollapsedHun,
- shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+ shelfHeightWithSpaceBefore = shelfWithSpaceBefore,
+ includesLessImportantNotification = includesLessImportantNotification,
)
)
}
@@ -381,16 +413,18 @@
fun updateResources() {
maxKeyguardNotifications =
infiniteIfNegative(
- if (notificationMinimalismPrototype()) {
- SystemProperties.getInt(
- "persist.notification_minimalism_prototype.lock_screen_max_notifs",
- 1
- )
+ if (NotificationMinimalismPrototype.V1.isEnabled) {
+ NotificationMinimalismPrototype.V1.maxNotifs
+ } else if (NotificationMinimalismPrototype.V2.isEnabled) {
+ 1
} else {
resources.getInteger(R.integer.keyguard_max_notification_count)
}
)
- maxNotificationsExcludesMedia = notificationMinimalismPrototype()
+ maxNotificationsExcludesMedia =
+ NotificationMinimalismPrototype.V1.isEnabled ||
+ NotificationMinimalismPrototype.V2.isEnabled
+ limitLockScreenToImportant = NotificationMinimalismPrototype.V2.isEnabled
dividerHeight =
max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index dacafc4..db544ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -38,16 +38,6 @@
*/
val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
- /**
- * The y-coordinate in px of top of the contents of the notification stack. This value can be
- * negative, if the stack is scrolled such that its top extends beyond the top edge of the
- * screen.
- */
- val stackTop = MutableStateFlow(0f)
-
- /** the bottom-most acceptable y-position for the bottom of the stack / shelf */
- val stackBottom = MutableStateFlow(0f)
-
/** the y position of the top of the HUN area */
val headsUpTop = MutableStateFlow(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 365ead6..e7acbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -72,12 +72,6 @@
val alphaForBrightnessMirror: StateFlow<Float> =
placeholderRepository.alphaForBrightnessMirror.asStateFlow()
- /** The y-coordinate in px of top of the contents of the notification stack. */
- val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow()
-
- /** The y-coordinate in px of bottom of the contents of the notification stack. */
- val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow()
-
/** The height of the keyguard's available space bounds */
val constrainedAvailableSpace: StateFlow<Int> =
placeholderRepository.constrainedAvailableSpace.asStateFlow()
@@ -121,16 +115,6 @@
viewHeightRepository.headsUpHeight.value = height
}
- /** Sets the y-coord in px of the top of the contents of the notification stack. */
- fun setStackTop(stackTop: Float) {
- placeholderRepository.stackTop.value = stackTop
- }
-
- /** Sets the y-coord in px of the bottom of the contents of the notification stack. */
- fun setStackBottom(stackBottom: Float) {
- placeholderRepository.stackBottom.value = stackBottom
- }
-
/** Sets whether the notification stack is scrolled to the top. */
fun setScrolledToTop(scrolledToTop: Boolean) {
placeholderRepository.scrolledToTop.value = scrolledToTop
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 3c44713..622d8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -79,8 +79,6 @@
}
launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
- launch { viewModel.stackTop.collect { view.setStackTop(it) } }
- launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 082f6b6..6137381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -130,10 +130,6 @@
val maxAlpha: Flow<Float> =
stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha")
- /** The y-coordinate in px of top of the contents of the notification stack. */
- val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
- /** The y-coordinate in px of bottom of the contents of the notification stack. */
- val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom")
/**
* Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
* further.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 736058a..97b86e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
@@ -57,18 +56,6 @@
interactor.setShadeScrimBounds(bounds)
}
- /** Notifies that the bounds of the notification placeholder have changed. */
- fun onStackBoundsChanged(
- top: Float,
- bottom: Float,
- ) {
- keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = top, bottom = bottom)
- )
- interactor.setStackTop(top)
- interactor.setStackBottom(bottom)
- }
-
/** Sets the available space */
fun onConstrainedAvailableSpaceChanged(height: Int) {
interactor.setConstrainedAvailableSpace(height)
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 0ba7b3c..3393321 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
@@ -26,6 +26,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -64,6 +65,7 @@
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
@@ -295,8 +297,7 @@
return combine(
isOnLockscreenWithoutShade,
keyguardTransitionInteractor.isInTransition(
- from = LOCKSCREEN,
- to = AOD,
+ edge = Edge.create(from = LOCKSCREEN, to = AOD)
),
::Pair
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 7d97428..8fb552f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -283,12 +283,11 @@
void awakenDreams();
/**
- * Handle a touch event while dreaming or on the glanceable hub when the touch was initiated
- * within a prescribed swipeable area. This method is provided for cases where swiping in
- * certain areas should be handled by CentralSurfaces instead (e.g. swiping hub open, opening
- * the notification shade over dream or hub).
+ * Handle a touch event while dreaming when the touch was initiated within a prescribed
+ * swipeable area. This method is provided for cases where swiping in certain areas of a dream
+ * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
*/
- void handleExternalShadeWindowTouch(MotionEvent event);
+ void handleDreamTouch(MotionEvent event);
boolean isBouncerShowing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e93c0f6..7dac77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -59,6 +59,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -82,6 +83,7 @@
private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final PanelExpansionInteractor mPanelExpansionInteractor;
+ private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final ShadeHeaderController mShadeHeaderController;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger;
@@ -121,6 +123,7 @@
ShadeController shadeController,
CommandQueue commandQueue,
PanelExpansionInteractor panelExpansionInteractor,
+ Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeHeaderController shadeHeaderController,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
MetricsLogger metricsLogger,
@@ -148,6 +151,7 @@
mShadeController = shadeController;
mCommandQueue = commandQueue;
mPanelExpansionInteractor = panelExpansionInteractor;
+ mShadeInteractorLazy = shadeInteractorLazy;
mShadeHeaderController = shadeHeaderController;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
mMetricsLogger = metricsLogger;
@@ -487,14 +491,23 @@
}
@Override
- public void togglePanel() {
- if (mPanelExpansionInteractor.isPanelExpanded()) {
+ public void toggleNotificationsPanel() {
+ if (mShadeInteractorLazy.get().isAnyExpanded().getValue()) {
mShadeController.animateCollapseShade();
} else {
mShadeController.animateExpandShade();
}
}
+ @Override
+ public void toggleQuickSettingsPanel() {
+ if (mShadeInteractorLazy.get().isQsExpanded().getValue()) {
+ mShadeController.animateCollapseShade();
+ } else {
+ mShadeController.animateExpandQs();
+ }
+ }
+
private boolean isGoingToSleep() {
return mWakefulnessLifecycle.getWakefulness()
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index d5e66ff..8af7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -79,7 +79,7 @@
override fun updateScrimController() {}
override fun shouldIgnoreTouch() = false
override fun isDeviceInteractive() = false
- override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
+ override fun handleDreamTouch(event: MotionEvent?) {}
override fun awakenDreams() {}
override fun isBouncerShowing() = false
override fun isBouncerShowingScrimmed() = false
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 aa55f37..d3d2b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2805,7 +2805,16 @@
mScrimController.setExpansionAffectsAlpha(!unlocking);
if (mAlternateBouncerInteractor.isVisibleState()) {
- if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
+ && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+ || mTransitionToFullShadeProgress > 0f)) {
+ // Assume scrim state for shade is already correct and do nothing
+ } else {
+ // Safeguard which prevents the scrim from being stuck in the wrong state
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ }
+ } else {
if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
&& (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f)) {
@@ -2814,7 +2823,6 @@
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
}
}
-
// This will cancel the keyguardFadingAway animation if it is running. We need to do
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
@@ -2932,8 +2940,8 @@
};
@Override
- public void handleExternalShadeWindowTouch(MotionEvent event) {
- getNotificationShadeWindowViewController().handleExternalTouch(event);
+ public void handleDreamTouch(MotionEvent event) {
+ getNotificationShadeWindowViewController().handleDreamTouch(event);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index f219b9d..2b26e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -54,7 +54,6 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.DisableStateTracker;
@@ -133,9 +132,6 @@
private View mSystemIconsContainer;
private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
- // TODO(b/273443374): remove
- private NotificationMediaManager mNotificationMediaManager;
-
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -302,7 +298,6 @@
@Main Executor mainExecutor,
@Background Executor backgroundExecutor,
KeyguardLogger logger,
- NotificationMediaManager notificationMediaManager,
StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory
) {
super(view);
@@ -357,7 +352,6 @@
/* mask2= */ DISABLE2_SYSTEM_ICONS,
this::updateViewState
);
- mNotificationMediaManager = notificationMediaManager;
mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 74182fc..fe001b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -64,6 +64,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -71,6 +72,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -454,23 +456,32 @@
};
// PRIMARY_BOUNCER->GONE
- collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE),
+ collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(PRIMARY_BOUNCER, GONE)),
mBouncerToGoneTransition, mMainDispatcher);
collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
// ALTERNATE_BOUNCER->GONE
- collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE),
+ collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(ALTERNATE_BOUNCER, Scenes.Gone),
+ Edge.Companion.create(ALTERNATE_BOUNCER, GONE)),
mBouncerToGoneTransition, mMainDispatcher);
collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
// LOCKSCREEN<->GLANCEABLE_HUB
+ collectFlow(
+ behindScrim,
+ mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(LOCKSCREEN, Scenes.Communal),
+ Edge.Companion.create(LOCKSCREEN, GLANCEABLE_HUB)),
+ mGlanceableHubConsumer,
+ mMainDispatcher);
collectFlow(behindScrim,
- mKeyguardTransitionInteractor.transition(LOCKSCREEN, GLANCEABLE_HUB),
- mGlanceableHubConsumer, mMainDispatcher);
- collectFlow(behindScrim,
- mKeyguardTransitionInteractor.transition(GLANCEABLE_HUB, LOCKSCREEN),
+ mKeyguardTransitionInteractor.transition(
+ Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
+ Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
mGlanceableHubConsumer, mMainDispatcher);
}
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 f0dab3b..fa88be5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -696,6 +696,7 @@
Trace.beginSection("StatusBarKeyguardViewManager#show");
mNotificationShadeWindowController.setKeyguardShowing(true);
if (SceneContainerFlag.isEnabled()) {
+ // TODO(b/336581871): add sceneState?
mSceneInteractorLazy.get().changeScene(
Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
}
@@ -1548,7 +1549,8 @@
}
if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+ "SBKVM#keyguardAuthenticated");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index a6284e3..4505a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -196,7 +196,22 @@
// The group isn't expanded, let's make sure it's visible!
mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
}
- row.setUserExpanded(true);
+
+ if (android.app.Flags.compactHeadsUpNotificationReply()
+ && row.isCompactConversationHeadsUpOnScreen()) {
+ // Notification can be system expanded true and it is set user expanded in
+ // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType doesn't
+ // change. To expand huning notification properly, we need set userExpanded false.
+ if (!row.isPinned() && row.isExpanded()) {
+ row.setUserExpanded(false);
+ }
+ // expand notification emits expanded information to HUN listener.
+ row.expandNotification();
+ } else {
+ // Note: Since Normal HUN has remote input view in it, we don't expect to hit
+ // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN.
+ row.setUserExpanded(true);
+ }
row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 4fc11df..a858fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -119,7 +119,7 @@
private MultiSourceMinAlphaController mEndSideAlphaController;
private LinearLayout mEndSideContent;
private View mClockView;
- private View mOngoingCallChip;
+ private View mOngoingActivityChip;
private View mNotificationIconAreaInner;
// Visibilities come in from external system callers via disable flags, but we also sometimes
// modify the visibilities internally. We need to store both so that we don't accidentally
@@ -345,7 +345,7 @@
mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
mClockView = mStatusBar.findViewById(R.id.clock);
- mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
+ mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip);
showEndSideContent(false);
showClock(false);
initOperatorName();
@@ -594,9 +594,9 @@
// so if the icons are disabled then the call chip should be, too.)
boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
if (showOngoingCallChip) {
- showOngoingCallChip(animate);
+ showOngoingActivityChip(animate);
} else {
- hideOngoingCallChip(animate);
+ hideOngoingActivityChip(animate);
}
mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
}
@@ -688,14 +688,19 @@
animateShow(mClockView, animate);
}
- /** Hides the ongoing call chip. */
- public void hideOngoingCallChip(boolean animate) {
- animateHiddenState(mOngoingCallChip, View.GONE, animate);
+ /** Hides the ongoing activity chip. */
+ private void hideOngoingActivityChip(boolean animate) {
+ animateHiddenState(mOngoingActivityChip, View.GONE, animate);
}
- /** Displays the ongoing call chip. */
- public void showOngoingCallChip(boolean animate) {
- animateShow(mOngoingCallChip, animate);
+ /**
+ * Displays the ongoing activity chip.
+ *
+ * For now, this chip will only ever contain the ongoing call information, but after b/332662551
+ * feature is implemented, it will support different kinds of ongoing activities.
+ */
+ private void showOngoingActivityChip(boolean animate) {
+ animateShow(mOngoingActivityChip, animate);
}
/**
@@ -803,7 +808,7 @@
private void initOngoingCallChip() {
mOngoingCallController.addCallback(mOngoingCallListener);
- mOngoingCallController.setChipView(mOngoingCallChip);
+ mOngoingCallController.setChipView(mOngoingActivityChip);
}
@Override
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 ec88b6c..a7d4ce3 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
@@ -36,6 +36,8 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -145,8 +147,8 @@
fun setChipView(chipView: View) {
tearDownChipView()
this.chipView = chipView
- val backgroundView: OngoingCallBackgroundContainer? =
- chipView.findViewById(R.id.ongoing_call_chip_background)
+ val backgroundView: ChipBackgroundContainer? =
+ chipView.findViewById(R.id.ongoing_activity_chip_background)
backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
if (hasOngoingCall()) {
updateChip()
@@ -226,7 +228,7 @@
if (callNotificationInfo == null) { return }
val currentChipView = chipView
val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
val intent = callNotificationInfo?.intent
if (currentChipView != null && backgroundView != null && intent != null) {
currentChipView.setOnClickListener {
@@ -271,8 +273,8 @@
@VisibleForTesting
fun tearDownChipView() = chipView?.getTimeView()?.stop()
- private fun View.getTimeView(): OngoingCallChronometer? {
- return this.findViewById(R.id.ongoing_call_chip_time)
+ private fun View.getTimeView(): ChipChronometer? {
+ return this.findViewById(R.id.ongoing_activity_chip_time)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9c78ab4..886481e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index cc87e8a..0a6e95e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -19,6 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -81,12 +82,12 @@
) : CollapsedStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
keyguardTransitionInteractor
- .isInTransition(LOCKSCREEN, OCCLUDED)
+ .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
keyguardTransitionInteractor
- .transition(LOCKSCREEN, DREAMING)
+ .transition(Edge.create(from = LOCKSCREEN, to = DREAMING))
.filter { it.transitionState == TransitionState.STARTED }
.map {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index fa8a7d8..8b48bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
@@ -30,12 +31,14 @@
* succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
*/
@SysUISingleton
-class AvalancheController @Inject constructor(
+class AvalancheController
+@Inject
+constructor(
dumpManager: DumpManager,
) : Dumpable {
private val tag = "AvalancheController"
- private val debug = false
+ private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
// HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
@VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -79,7 +82,7 @@
val fn = "[$label] => AvalancheController.update [${getKey(entry)}]"
if (entry == null) {
log { "Entry is NULL, stop update." }
- return;
+ return
}
if (debug) {
debugRunnableLabelMap[runnable] = label
@@ -106,7 +109,10 @@
if (isOnlyNextEntry) {
// HeadsUpEntry.updateEntry recursively calls AvalancheController#update
// and goes to the isShowing case above
- headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ headsUpEntryShowing!!.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ "avalanche duration update")
}
}
logState("after $fn")
@@ -142,9 +148,12 @@
} else if (isShowing(entry)) {
log { "$fn => [remove showing ${getKey(entry)}]" }
previousHunKey = getKey(headsUpEntryShowing)
-
+ // Show the next HUN before removing this one, so that we don't tell listeners
+ // onHeadsUpPinnedModeChanged, which causes
+ // NotificationPanelViewController.updateTouchableRegion to hide the window while the
+ // HUN is animating out, resulting in a flicker.
+ showNext()
runnable.run()
- showNextAfterRemove()
} else {
log { "$fn => [removing untracked ${getKey(entry)}]" }
}
@@ -247,7 +256,7 @@
}
}
- private fun showNextAfterRemove() {
+ private fun showNext() {
log { "SHOW NEXT" }
headsUpEntryShowing = null
@@ -294,17 +303,21 @@
private fun getStateStr(): String {
return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr"
+ "\nPREVIOUS: [$previousHunKey]" +
+ "\nNEXT LIST: $nextListStr" +
+ "\nNEXT MAP: $nextMapStr" +
+ "\nDROPPED: $dropSetStr"
}
private fun logState(reason: String) {
- log { "\n================================================================================="}
+ log {
+ "\n================================================================================="
+ }
log { "STATE $reason" }
log { getStateStr() }
- log { "=================================================================================\n"}
+ log {
+ "=================================================================================\n"
+ }
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index b8318a7..a7fe49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -39,6 +39,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -114,7 +115,8 @@
mUiEventLogger = uiEventLogger;
mAvalancheController = avalancheController;
Resources resources = context.getResources();
- mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+ ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
R.integer.sticky_heads_up_notification_time);
mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -765,11 +767,23 @@
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+ updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason);
+ }
+
+ /**
+ * Updates an entry's removal time.
+ * @param updatePostTime whether or not to refresh the post time
+ * @param updateEarliestRemovalTime whether this update should further delay removal
+ */
+ public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime,
+ @Nullable String reason) {
Runnable runnable = () -> {
mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ if (updateEarliestRemovalTime) {
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
+ }
if (updatePostTime) {
mPostTime = Math.max(mPostTime, now);
@@ -785,7 +799,9 @@
FinishTimeUpdater finishTimeCalculator = () -> {
final long finishTime = calculateFinishTime();
final long now = mSystemClock.elapsedRealtime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ final long timeLeft = NotificationThrottleHun.isEnabled()
+ ? Math.max(finishTime, mEarliestRemovalTime) - now
+ : Math.max(finishTime - now, mMinimumDisplayTime);
return timeLeft;
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -818,13 +834,6 @@
}
public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
- boolean isPinned = mEntry.isRowPinned();
- boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
- if (isPinned && !otherPinned) {
- return -1;
- } else if (!isPinned && otherPinned) {
- return 1;
- }
boolean selfFullscreen = hasFullScreenIntent(mEntry);
boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
if (selfFullscreen && !otherFullscreen) {
@@ -851,6 +860,13 @@
}
public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+ boolean isPinned = mEntry.isRowPinned();
+ boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
+ if (isPinned && !otherPinned) {
+ return -1;
+ } else if (!isPinned && otherPinned) {
+ return 1;
+ }
int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
if (nonTimeCompareResult != 0) {
return nonTimeCompareResult;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java
new file mode 100644
index 0000000..efeb2f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java
@@ -0,0 +1,322 @@
+/*
+ * 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.theme;
+
+import com.google.ux.material.libmonet.dynamiccolor.ContrastCurve;
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
+import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
+import com.google.ux.material.libmonet.dynamiccolor.ToneDeltaPair;
+import com.google.ux.material.libmonet.dynamiccolor.TonePolarity;
+
+class CustomDynamicColors {
+ private final MaterialDynamicColors mMdc;
+
+ CustomDynamicColors(boolean isExtendedFidelity) {
+ this.mMdc = new MaterialDynamicColors(isExtendedFidelity);
+ }
+
+ // CLOCK COLORS
+
+ public DynamicColor widgetBackground() {
+ return new DynamicColor(
+ /* name= */ "widget_background",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 20.0 : 95.0,
+ /* isBackground= */ true,
+ /* background= */ null,
+ /* secondBackground= */ null,
+ /* contrastCurve= */ null,
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor clockHour() {
+ return new DynamicColor(
+ /* name= */ "clock_hour",
+ /* palette= */ (s) -> s.secondaryPalette,
+ /* tone= */ (s) -> s.isDark ? 30.0 : 60.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> widgetBackground(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 4.0, 5.0, 15.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(clockHour(), clockMinute(), 10.0, TonePolarity.DARKER,
+ false));
+ }
+
+ public DynamicColor clockMinute() {
+ return new DynamicColor(
+ /* name= */ "clock_minute",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> widgetBackground(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 6.5, 10.0, 15.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor clockSecond() {
+ return new DynamicColor(
+ /* name= */ "clock_second",
+ /* palette= */ (s) -> s.tertiaryPalette,
+ /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> widgetBackground(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor weatherTemp() {
+ return new DynamicColor(
+ /* name= */ "clock_second",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 55.0 : 80.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> widgetBackground(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ // THEME APP ICONS
+
+ public DynamicColor themeApp() {
+ return new DynamicColor(
+ /* name= */ "theme_app",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 90.0 : 30.0, // Adjusted values
+ /* isBackground= */ true,
+ /* background= */ null,
+ /* secondBackground= */ null,
+ /* contrastCurve= */ null,
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor onThemeApp() {
+ return new DynamicColor(
+ /* name= */ "on_theme_app",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 40.0 : 80.0, // Adjusted values
+ /* isBackground= */ false,
+ /* background= */ (s) -> themeApp(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 10.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor themeAppRing() {
+ return new DynamicColor(
+ /* name= */ "theme_app_ring",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> 70.0,
+ /* isBackground= */ true,
+ /* background= */ null,
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor themeNotif() {
+ return new DynamicColor(
+ /* name= */ "theme_notif",
+ /* palette= */ (s) -> s.tertiaryPalette,
+ /* tone= */ (s) -> s.isDark ? 80.0 : 90.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> themeAppRing(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(themeNotif(), themeAppRing(), 10.0, TonePolarity.NEARER,
+ false));
+ }
+
+ // SUPER G COLORS
+
+ public DynamicColor brandA() {
+ return new DynamicColor(
+ /* name= */ "brand_a",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 40.0 : 80.0,
+ /* isBackground= */ true,
+ /* background= */ (s) -> mMdc.surfaceContainerLow(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 17.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(brandA(), brandB(), 10.0, TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor brandB() {
+ return new DynamicColor(
+ /* name= */ "brand_b",
+ /* palette= */ (s) -> s.secondaryPalette,
+ /* tone= */ (s) -> s.isDark ? 70.0 : 98.0,
+ /* isBackground= */ true,
+ /* background= */ (s) -> mMdc.surfaceContainerLow(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 3.0, 6.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(brandB(), brandC(), 10.0, TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor brandC() {
+ return new DynamicColor(
+ /* name= */ "brand_c",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> s.isDark ? 50.0 : 60.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> mMdc.surfaceContainerLow(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 9.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(brandC(), brandD(), 10.0, TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor brandD() {
+ return new DynamicColor(
+ /* name= */ "brand_d",
+ /* palette= */ (s) -> s.tertiaryPalette,
+ /* tone= */ (s) -> s.isDark ? 59.0 : 90.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> mMdc.surfaceContainerLow(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 13.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(brandD(), brandA(), 10.0, TonePolarity.NEARER, false));
+ }
+
+ // QUICK SETTING TIILES
+
+ public DynamicColor underSurface() {
+ return new DynamicColor(
+ /* name= */ "under_surface",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> 0.0,
+ /* isBackground= */ true,
+ /* background= */ null,
+ /* secondBackground= */ null,
+ /* contrastCurve= */ null,
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor shadeActive() {
+ return new DynamicColor(
+ /* name= */ "shade_active",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> 90.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> underSurface(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.5, 7.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(shadeActive(), shadeInactive(), 30.0, TonePolarity.LIGHTER,
+ false));
+ }
+
+ public DynamicColor onShadeActive() {
+ return new DynamicColor(
+ /* name= */ "on_shade_active",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> 10.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> shadeActive(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(onShadeActive(), onShadeActiveVariant(), 20.0,
+ TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor onShadeActiveVariant() {
+ return new DynamicColor(
+ /* name= */ "on_shade_active_variant",
+ /* palette= */ (s) -> s.primaryPalette,
+ /* tone= */ (s) -> 30.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> shadeActive(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(onShadeActiveVariant(), onShadeActive(), 20.0,
+ TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor shadeInactive() {
+ return new DynamicColor(
+ /* name= */ "shade_inactive",
+ /* palette= */ (s) -> s.neutralPalette,
+ /* tone= */ (s) -> 20.0,
+ /* isBackground= */ true,
+ /* background= */ (s) -> underSurface(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+ /* toneDeltaPair= */(s) -> new ToneDeltaPair(shadeInactive(), shadeDisabled(), 15.0,
+ TonePolarity.LIGHTER, false));
+ }
+
+ public DynamicColor onShadeInactive() {
+ return new DynamicColor(
+ /* name= */ "on_shade_inactive",
+ /* palette= */ (s) -> s.neutralVariantPalette,
+ /* tone= */ (s) -> 90.0,
+ /* isBackground= */ true,
+ /* background= */ (s) -> shadeInactive(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(onShadeInactive(), onShadeInactiveVariant(), 10.0,
+ TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor onShadeInactiveVariant() {
+ return new DynamicColor(
+ /* name= */ "on_shade_inactive_variant",
+ /* palette= */ (s) -> s.neutralVariantPalette,
+ /* tone= */ (s) -> 80.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> shadeInactive(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+ /* toneDeltaPair= */
+ (s) -> new ToneDeltaPair(onShadeInactiveVariant(), onShadeInactive(), 10.0,
+ TonePolarity.NEARER, false));
+ }
+
+ public DynamicColor shadeDisabled() {
+ return new DynamicColor(
+ /* name= */ "shade_disabled",
+ /* palette= */ (s) -> s.neutralPalette,
+ /* tone= */ (s) -> 4.0,
+ /* isBackground= */ false,
+ /* background= */ (s) -> underSurface(),
+ /* secondBackground= */ null,
+ /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+ /* toneDeltaPair= */ null);
+ }
+
+ public DynamicColor overviewBackground() {
+ return new DynamicColor(
+ /* name= */ "overview_background",
+ /* palette= */ (s) -> s.neutralVariantPalette,
+ /* tone= */ (s) -> s.isDark ? 80.0 : 35.0,
+ /* isBackground= */ true,
+ /* background= */ null,
+ /* secondBackground= */ null,
+ /* contrastCurve= */null,
+ /* toneDeltaPair= */ null);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index a983d2f..3518759 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -103,5 +103,33 @@
Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()),
)
}
+
+ @JvmStatic
+ fun getCustomColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+ val customMdc = CustomDynamicColors(isExtendedFidelity)
+ return arrayListOf(
+ Pair.create("widget_background", customMdc.widgetBackground()),
+ Pair.create("clock_hour", customMdc.clockHour()),
+ Pair.create("clock_minute", customMdc.clockMinute()),
+ Pair.create("clock_second", customMdc.weatherTemp()),
+ Pair.create("theme_app", customMdc.themeApp()),
+ Pair.create("on_theme_app", customMdc.onThemeApp()),
+ Pair.create("theme_app_ring", customMdc.themeAppRing()),
+ Pair.create("on_theme_app_ring", customMdc.themeNotif()),
+ Pair.create("brand_a", customMdc.brandA()),
+ Pair.create("brand_b", customMdc.brandB()),
+ Pair.create("brand_c", customMdc.brandC()),
+ Pair.create("brand_d", customMdc.brandD()),
+ Pair.create("under_surface", customMdc.underSurface()),
+ Pair.create("shade_active", customMdc.shadeActive()),
+ Pair.create("on_shade_active", customMdc.onShadeActive()),
+ Pair.create("on_shade_active_variant", customMdc.onShadeActiveVariant()),
+ Pair.create("shade_inactive", customMdc.shadeInactive()),
+ Pair.create("on_shade_inactive", customMdc.onShadeInactive()),
+ Pair.create("on_shade_inactive_variant", customMdc.onShadeInactiveVariant()),
+ Pair.create("shade_disabled", customMdc.shadeDisabled()),
+ Pair.create("overview_background", customMdc.overviewBackground())
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5c3bbb7..d256c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -56,6 +56,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -84,6 +85,7 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
import org.json.JSONException;
@@ -631,29 +633,33 @@
protected FabricatedOverlay createDynamicOverlay() {
FabricatedOverlay overlay = newFabricatedOverlay("dynamic");
- assignDynamicPaletteToOverlay(overlay, true /* isDark */);
- assignDynamicPaletteToOverlay(overlay, false /* isDark */);
- assignFixedColorsToOverlay(overlay);
+ //Themed Colors
+ assignColorsToOverlay(overlay, DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled),
+ false);
+ // Fixed Colors
+ assignColorsToOverlay(overlay, DynamicColors.getFixedColorsMapped(mIsFidelityEnabled),
+ true);
+ //Custom Colors
+ assignColorsToOverlay(overlay, DynamicColors.getCustomColorsMapped(mIsFidelityEnabled),
+ false);
return overlay;
}
- private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
- String suffix = isDark ? "dark" : "light";
- ColorScheme scheme = isDark ? mDarkColorScheme : mLightColorScheme;
- DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
- String resourceName = "android:color/system_" + p.first + "_" + suffix;
- int colorValue = p.second.getArgb(scheme.getMaterialScheme());
- overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
- null /* configuration */);
- });
- }
+ private void assignColorsToOverlay(FabricatedOverlay overlay,
+ List<Pair<String, DynamicColor>> colors, Boolean isFixed) {
+ colors.forEach(p -> {
+ String prefix = "android:color/system_" + p.first;
- private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
- DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
- String resourceName = "android:color/system_" + p.first;
- int colorValue = p.second.getArgb(mLightColorScheme.getMaterialScheme());
- overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
- null /* configuration */);
+ if (isFixed) {
+ overlay.setResourceValue(prefix, TYPE_INT_COLOR_ARGB8,
+ p.second.getArgb(mLightColorScheme.getMaterialScheme()), null);
+ return;
+ }
+
+ overlay.setResourceValue(prefix + "_light", TYPE_INT_COLOR_ARGB8,
+ p.second.getArgb(mLightColorScheme.getMaterialScheme()), null);
+ overlay.setResourceValue(prefix + "_dark", TYPE_INT_COLOR_ARGB8,
+ p.second.getArgb(mDarkColorScheme.getMaterialScheme()), null);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index bfed0c4..0a1724c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -70,8 +70,10 @@
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
init {
- resumeSessionReceiver.register()
- resetOrExitSessionReceiver.register()
+ if (applicationContext.userId == UserHandle.USER_SYSTEM) {
+ resumeSessionReceiver.register()
+ resetOrExitSessionReceiver.register()
+ }
}
/** Notifies that the device has finished booting. */
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 9339651..516cb46 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -533,7 +533,7 @@
targetUserId = targetUserId,
::showDialog,
::dismissDialog,
- ::selectUser,
+ ::switchUser
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 46ce5f2..1ec86a4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -97,3 +97,12 @@
fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
return combine(flow1, flow2, bifunction)
}
+
+fun <A, B, C, R> combineFlows(
+ flow1: Flow<A>,
+ flow2: Flow<B>,
+ flow3: Flow<C>,
+ trifunction: (A, B, C) -> R
+): Flow<R> {
+ return combine(flow1, flow2, flow3, trifunction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b436eb9..e56893a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -282,7 +282,7 @@
@GuardedBy("mSafetyWarningLock")
private CsdWarningDialog mCsdDialog;
private boolean mHovering = false;
- private final boolean mShowActiveStreamOnly;
+ private final boolean mIsTv;
private boolean mConfigChanged = false;
private boolean mIsAnimatingDismiss = false;
private boolean mHasSeenODICaptionsTooltip;
@@ -343,7 +343,7 @@
mConfigurationController = configurationController;
mMediaOutputDialogManager = mediaOutputDialogManager;
mCsdWarningDialogFactory = csdWarningDialogFactory;
- mShowActiveStreamOnly = showActiveStreamOnly();
+ mIsTv = isTv();
mHasSeenODICaptionsTooltip =
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
mShowLowMediaVolumeIcon =
@@ -1632,7 +1632,7 @@
Trace.endSection();
}
- private boolean showActiveStreamOnly() {
+ private boolean isTv() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|| mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
}
@@ -1644,7 +1644,7 @@
return true;
}
- if (!mShowActiveStreamOnly) {
+ if (!mIsTv) {
if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
return mShowA11yStream;
}
@@ -2089,6 +2089,11 @@
}
final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
if (progress != newProgress) {
+ if (mIsTv) {
+ // don't animate slider on TVs
+ row.slider.setProgress(newProgress, false);
+ return;
+ }
if (mShowing && rowVisible) {
// animate!
if (row.anim != null && row.anim.isRunning()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 155102c9..3696108 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -27,6 +27,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractorImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -41,6 +43,11 @@
impl: LocalMediaRepositoryFactoryImpl
): LocalMediaRepositoryFactory
+ @Binds
+ fun bindMediaControllerInteractor(
+ impl: MediaControllerInteractorImpl
+ ): MediaControllerInteractor
+
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
new file mode 100644
index 0000000..4812765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.volume.panel.component.mediaoutput.domain.interactor
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+interface MediaControllerInteractor {
+
+ /** [MediaController.Callback] flow representation. */
+ fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel>
+}
+
+@SysUISingleton
+class MediaControllerInteractorImpl
+@Inject
+constructor(
+ @Background private val backgroundHandler: Handler,
+) : MediaControllerInteractor {
+
+ override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
+ return conflatedCallbackFlow {
+ val callback = MediaControllerCallbackProducer(this)
+ mediaController.registerCallback(callback, backgroundHandler)
+ awaitClose { mediaController.unregisterCallback(callback) }
+ }
+ }
+}
+
+private class MediaControllerCallbackProducer(
+ private val producingScope: ProducerScope<MediaControllerChangeModel>
+) : MediaController.Callback() {
+
+ override fun onSessionDestroyed() {
+ send(MediaControllerChangeModel.SessionDestroyed)
+ }
+
+ override fun onSessionEvent(event: String, extras: Bundle?) {
+ send(MediaControllerChangeModel.SessionEvent(event, extras))
+ }
+
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
+ send(MediaControllerChangeModel.PlaybackStateChanged(state))
+ }
+
+ override fun onMetadataChanged(metadata: MediaMetadata?) {
+ send(MediaControllerChangeModel.MetadataChanged(metadata))
+ }
+
+ override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
+ send(MediaControllerChangeModel.QueueChanged(queue))
+ }
+
+ override fun onQueueTitleChanged(title: CharSequence?) {
+ send(MediaControllerChangeModel.QueueTitleChanged(title))
+ }
+
+ override fun onExtrasChanged(extras: Bundle?) {
+ send(MediaControllerChangeModel.ExtrasChanged(extras))
+ }
+
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+ send(MediaControllerChangeModel.AudioInfoChanged(info))
+ }
+
+ private fun send(change: MediaControllerChangeModel) {
+ producingScope.launch { producingScope.send(change) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index dc73344..599bd73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -18,11 +18,9 @@
import android.media.session.MediaController
import android.media.session.PlaybackState
-import android.os.Handler
-import com.android.settingslib.volume.data.repository.MediaControllerChange
import com.android.settingslib.volume.data.repository.MediaControllerRepository
-import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -45,38 +43,39 @@
@Inject
constructor(
@Background private val backgroundCoroutineContext: CoroutineContext,
- @Background private val backgroundHandler: Handler,
+ private val mediaControllerInteractor: MediaControllerInteractor,
private val mediaControllerRepository: MediaControllerRepository,
) {
/** [PlaybackState] changes for the [MediaDeviceSession]. */
fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> {
return stateChanges(session) {
- emit(MediaControllerChange.PlaybackStateChanged(it.playbackState))
+ emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState))
}
- .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class)
+ .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class)
.map { it.state }
}
/** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
return stateChanges(session) {
- emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo))
+ emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo))
}
- .filterIsInstance(MediaControllerChange.AudioInfoChanged::class)
+ .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class)
.map { it.info }
}
private fun stateChanges(
session: MediaDeviceSession,
- onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit,
- ): Flow<MediaControllerChange?> =
+ onStart:
+ suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit,
+ ): Flow<MediaControllerChangeModel?> =
mediaControllerRepository.activeSessions
.flatMapLatest { controllers ->
val controller: MediaController =
findControllerForSession(controllers, session)
?: return@flatMapLatest flowOf(null)
- controller.stateChanges(backgroundHandler).onStart { onStart(controller) }
+ mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) }
}
.flowOn(backgroundCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index b00829e..9fbd79a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -19,12 +19,10 @@
import android.content.pm.PackageManager
import android.media.VolumeProvider
import android.media.session.MediaController
-import android.os.Handler
import android.util.Log
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
-import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
@@ -61,7 +59,7 @@
@VolumePanelScope private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
mediaControllerRepository: MediaControllerRepository,
- @Background private val backgroundHandler: Handler,
+ private val mediaControllerInteractor: MediaControllerInteractor,
) {
private val activeMediaControllers: Flow<MediaControllers> =
@@ -194,7 +192,10 @@
return flowOf(null)
}
- return stateChanges(backgroundHandler).map { this }.onStart { emit(this@stateChanges) }
+ return mediaControllerInteractor
+ .stateChanges(this)
+ .map { this }
+ .onStart { emit(this@stateChanges) }
}
private data class MediaControllers(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
new file mode 100644
index 0000000..ef5a44a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+
+/** Models particular change event received by [MediaController.Callback]. */
+sealed interface MediaControllerChangeModel {
+
+ data object SessionDestroyed : MediaControllerChangeModel
+
+ data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChangeModel
+
+ data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChangeModel
+
+ data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChangeModel
+
+ data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
+ MediaControllerChangeModel
+
+ data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChangeModel
+
+ data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel
+
+ data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) :
+ MediaControllerChangeModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 1568e8c0..2e29bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,6 +20,7 @@
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.SetWallpaperFlags;
+import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased;
import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.Nullable;
@@ -128,8 +129,17 @@
* and if the count is 0, unload the bitmap
*/
private int mBitmapUsages = 0;
+
+ /**
+ * Main lock for long operations (loading the bitmap or processing colors).
+ */
private final Object mLock = new Object();
+ /**
+ * Lock for SurfaceHolder operations. Should only be acquired after the main lock.
+ */
+ private final Object mSurfaceLock = new Object();
+
CanvasEngine() {
super();
setFixedSizeAllowed(true);
@@ -223,6 +233,12 @@
if (DEBUG) {
Log.i(TAG, "onSurfaceDestroyed");
}
+ if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+ synchronized (mSurfaceLock) {
+ mSurfaceHolder = null;
+ }
+ return;
+ }
mLongExecutor.execute(this::onSurfaceDestroyedSynchronized);
}
@@ -259,7 +275,7 @@
}
private void drawFrameInternal() {
- if (mSurfaceHolder == null) {
+ if (mSurfaceHolder == null && !fixImageWallpaperCrashSurfaceAlreadyReleased()) {
Log.i(TAG, "attempt to draw a frame without a valid surface");
return;
}
@@ -268,6 +284,19 @@
if (!isBitmapLoaded()) {
loadWallpaperAndDrawFrameInternal();
} else {
+ if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+ synchronized (mSurfaceLock) {
+ if (mSurfaceHolder == null) {
+ Log.i(TAG, "Surface released before the image could be drawn");
+ return;
+ }
+ mBitmapUsages++;
+ drawFrameOnCanvas(mBitmap);
+ reportEngineShown(false);
+ unloadBitmapIfNotUsedInternal();
+ return;
+ }
+ }
mBitmapUsages++;
drawFrameOnCanvas(mBitmap);
reportEngineShown(false);
@@ -328,9 +357,14 @@
mBitmap.recycle();
}
mBitmap = null;
-
- final Surface surface = getSurfaceHolder().getSurface();
- surface.hwuiDestroy();
+ if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+ synchronized (mSurfaceLock) {
+ if (mSurfaceHolder != null) mSurfaceHolder.getSurface().hwuiDestroy();
+ }
+ } else {
+ final Surface surface = getSurfaceHolder().getSurface();
+ surface.hwuiDestroy();
+ }
mWallpaperManager.forgetLoadedWallpaper();
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index b86a7c9..e073f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -98,7 +98,7 @@
CoreStartable,
CommandQueue.Callbacks {
private static final String TAG = WMShell.class.getName();
- private static final int INVALID_SYSUI_STATE_MASK =
+ private static final long INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_DIALOG_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6f550ba..5702a8c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -21,14 +21,18 @@
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.core.LogLevel
@@ -68,8 +72,9 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import com.android.systemui.Flags as AConfigFlags
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -319,26 +324,16 @@
fun listenForDozeAmountTransition_updatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(
- keyguardTransitionInteractor.transition(
- KeyguardState.LOCKSCREEN,
- KeyguardState.AOD
- )
- )
+ whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)))
.thenReturn(transitionStep)
- whenever(
- keyguardTransitionInteractor.transition(
- KeyguardState.AOD,
- KeyguardState.LOCKSCREEN
- )
- )
+ whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)))
.thenReturn(transitionStep)
val job = underTest.listenForDozeAmountTransition(this)
transitionStep.value =
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
+ from = LOCKSCREEN,
+ to = AOD,
value = 0.4f,
transitionState = TransitionState.RUNNING,
)
@@ -353,14 +348,14 @@
fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ whenever(keyguardTransitionInteractor.transitionStepsToState(AOD))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToAodTransition(this)
transitionStep.value =
TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
+ from = GONE,
+ to = AOD,
transitionState = TransitionState.STARTED,
)
yield()
@@ -374,16 +369,16 @@
fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
- .thenReturn(transitionStep)
+ whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN))
+ .thenReturn(transitionStep)
val job = underTest.listenForAnyStateToLockscreenTransition(this)
transitionStep.value =
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED,
- )
+ TransitionStep(
+ from = OCCLUDED,
+ to = LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
yield()
verify(animations, times(2)).doze(0f)
@@ -395,37 +390,37 @@
fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ whenever(keyguardTransitionInteractor.transitionStepsToState(AOD))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToAodTransition(this)
transitionStep.value =
TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
+ from = LOCKSCREEN,
+ to = AOD,
transitionState = TransitionState.STARTED,
)
yield()
verify(animations, never()).doze(1f)
- job.cancel()
- }
+ job.cancel()
+ }
@Test
fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
- .thenReturn(transitionStep)
+ whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN))
+ .thenReturn(transitionStep)
val job = underTest.listenForAnyStateToLockscreenTransition(this)
transitionStep.value =
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- transitionState = TransitionState.STARTED,
- )
+ TransitionStep(
+ from = AOD,
+ to = LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
yield()
verify(animations, never()).doze(0f)
@@ -437,16 +432,16 @@
fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING))
- .thenReturn(transitionStep)
+ whenever(keyguardTransitionInteractor.transitionStepsToState(DOZING))
+ .thenReturn(transitionStep)
val job = underTest.listenForAnyStateToDozingTransition(this)
transitionStep.value =
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DOZING,
- transitionState = TransitionState.STARTED,
- )
+ TransitionStep(
+ from = LOCKSCREEN,
+ to = DOZING,
+ transitionState = TransitionState.STARTED,
+ )
yield()
verify(animations, times(2)).doze(1f)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 51ceda3..f9fe5e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.KeyEvent;
@@ -125,8 +126,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
public void withFeatureFlagOn_oldMessage_isHidden() {
- mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
KeyguardAbsKeyInputViewController underTest = createTestObject();
underTest.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 6dc5b72..bbdd805 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
@@ -104,7 +105,7 @@
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
doAnswer(invocation -> {
mMagnification.mMagnificationSettingsControllerCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index bf6ca06..e371b39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -46,12 +46,12 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
private const val ON: Int = 1
private const val OFF: Int = 0
@@ -90,7 +90,7 @@
secureSettings = FakeSettings()
systemClock = FakeSystemClock()
backgroundDelayableExecutor = FakeExecutor(systemClock)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
fontScalingDialogDelegate =
spy(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index ebb6b48..8895a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -118,7 +119,7 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 8e4c155..fd37cad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -8,6 +8,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.Looper
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback
@@ -17,15 +18,20 @@
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
+import android.window.RemoteTransition
+import android.window.TransitionFilter
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.Flags
import com.android.systemui.util.mockito.any
+import com.android.wm.shell.shared.ShellTransitions
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -48,6 +54,7 @@
private val transitionContainer = LinearLayout(mContext)
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
+ private val testShellTransitions = FakeShellTransitions()
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -55,12 +62,16 @@
private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
@Before
fun setup() {
activityTransitionAnimator =
ActivityTransitionAnimator(
mainExecutor,
+ ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
+ testShellTransitions
+ ),
testTransitionAnimator,
testTransitionAnimator,
disableWmTimeout = true,
@@ -164,6 +175,34 @@
}
@Test
+ fun registersReturnIffCookieIsPresent() {
+ setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
+ `when`(callback.isOnKeyguard()).thenReturn(false)
+
+ startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
+
+ waitForIdleSync()
+ assertTrue(testShellTransitions.remotes.isEmpty())
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+
+ val controller =
+ object : DelegateTransitionAnimatorController(controller) {
+ override val transitionCookie
+ get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
+ }
+
+ startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
+
+ waitForIdleSync()
+ assertEquals(1, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ }
+
+ @Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationCancelled()
@@ -243,6 +282,35 @@
}
/**
+ * A fake implementation of [ShellTransitions] which saves filter-transition pairs locally and
+ * allows inspection.
+ */
+private class FakeShellTransitions : ShellTransitions {
+ val remotes = mutableMapOf<TransitionFilter, RemoteTransition>()
+ val remotesForTakeover = mutableMapOf<TransitionFilter, RemoteTransition>()
+
+ override fun registerRemote(filter: TransitionFilter, remoteTransition: RemoteTransition) {
+ remotes[filter] = remoteTransition
+ }
+
+ override fun registerRemoteForTakeover(
+ filter: TransitionFilter,
+ remoteTransition: RemoteTransition
+ ) {
+ remotesForTakeover[filter] = remoteTransition
+ }
+
+ override fun unregisterRemote(remoteTransition: RemoteTransition) {
+ while (remotes.containsValue(remoteTransition)) {
+ remotes.values.remove(remoteTransition)
+ }
+ while (remotesForTakeover.containsValue(remoteTransition)) {
+ remotesForTakeover.values.remove(remoteTransition)
+ }
+ }
+}
+
+/**
* A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called
* outside of the main thread.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index b31fe21..42fcd54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.animation
+import android.os.HandlerThread
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,6 +34,13 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
+ companion object {
+ private const val LAUNCH_CUJ = 0
+ private const val RETURN_CUJ = 1
+ }
+
+ private val interactionJankMonitor = FakeInteractionJankMonitor()
+
@Test
fun animatingOrphanViewDoesNotCrash() {
val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
@@ -47,4 +58,63 @@
GhostedViewTransitionAnimatorController(FrameLayout(mContext))
}
}
+
+ @Test
+ fun cujsAreLoggedCorrectly() {
+ val parent = FrameLayout(mContext)
+
+ val launchView = LaunchableFrameLayout(mContext)
+ parent.addView((launchView))
+ val launchController =
+ GhostedViewTransitionAnimatorController(
+ launchView,
+ launchCujType = LAUNCH_CUJ,
+ returnCujType = RETURN_CUJ,
+ interactionJankMonitor = interactionJankMonitor
+ )
+ launchController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ)
+ launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).isEmpty()
+ assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ)
+
+ val returnView = LaunchableFrameLayout(mContext)
+ parent.addView((returnView))
+ val returnController =
+ object : GhostedViewTransitionAnimatorController(
+ returnView,
+ launchCujType = LAUNCH_CUJ,
+ returnCujType = RETURN_CUJ,
+ interactionJankMonitor = interactionJankMonitor
+ ) {
+ override val isLaunching = false
+ }
+ returnController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ)
+ returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).isEmpty()
+ assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ)
+ }
+
+ /**
+ * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and
+ * allows inspection.
+ */
+ private class FakeInteractionJankMonitor : InteractionJankMonitor(
+ HandlerThread("testThread")
+ ) {
+ val ongoing: MutableSet<Int> = mutableSetOf()
+ val finished: MutableSet<Int> = mutableSetOf()
+
+ override fun begin(v: View?, cujType: Int): Boolean {
+ ongoing.add(cujType)
+ return true
+ }
+
+ override fun end(cujType: Int): Boolean {
+ ongoing.remove(cujType)
+ finished.add(cujType)
+ return true
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 7597e62..e81369d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -43,6 +43,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -150,6 +151,7 @@
private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+ private lateinit var iconProvider: IconProvider
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -178,6 +180,7 @@
biometricStatusInteractor =
BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
fingerprintRepository)
+ iconProvider = IconProvider(context)
// Set up default logo icon
whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
context.setMockPackageManager(packageManager)
@@ -655,7 +658,9 @@
context,
udfpsOverlayInteractor,
biometricStatusInteractor,
- udfpsUtils
+ udfpsUtils,
+ iconProvider,
+ activityTaskManager
),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index e0324df..4068404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.domain.interactor
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
@@ -47,19 +48,22 @@
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
-private const val TITLE = "hey there"
-private const val SUBTITLE = "ok"
-private const val DESCRIPTION = "football"
-private const val NEGATIVE_TEXT = "escape"
-
-private const val USER_ID = 8
-private const val CHALLENGE = 999L
-private const val OP_PACKAGE_NAME = "biometric.testapp"
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class PromptSelectorInteractorImplTest : SysuiTestCase() {
+ companion object {
+ private const val TITLE = "hey there"
+ private const val SUBTITLE = "ok"
+ private const val DESCRIPTION = "football"
+ private const val NEGATIVE_TEXT = "escape"
+
+ private const val USER_ID = 8
+ private const val CHALLENGE = 999L
+ private const val OP_PACKAGE_NAME = "biometric.testapp"
+ private val componentNameOverriddenForConfirmDeviceCredentialActivity =
+ ComponentName("not.com.android.settings", "testapp")
+ }
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -103,7 +107,19 @@
fun useBiometricsAndResetWithoutFallback() =
testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) }
- private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) {
+ @Test
+ fun useBiometricsAndResetOnConfirmDeviceCredentialActivity() =
+ testScope.runTest {
+ useBiometricsAndReset(
+ allowCredentialFallback = true,
+ setComponentNameForConfirmDeviceCredentialActivity = true
+ )
+ }
+
+ private fun TestScope.useBiometricsAndReset(
+ allowCredentialFallback: Boolean,
+ setComponentNameForConfirmDeviceCredentialActivity: Boolean = false
+ ) {
setUserCredentialType(isPassword = true)
val confirmationRequired = true
@@ -117,6 +133,10 @@
Authenticators.BIOMETRIC_STRONG
}
isDeviceCredentialAllowed = allowCredentialFallback
+ componentNameForConfirmDeviceCredentialActivity =
+ if (setComponentNameForConfirmDeviceCredentialActivity)
+ componentNameOverriddenForConfirmDeviceCredentialActivity
+ else null
}
val currentPrompt by collectLastValue(interactor.prompt)
@@ -143,6 +163,12 @@
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
assertThat(promptKind!!.isBiometric()).isTrue()
+ assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity)
+ .isEqualTo(
+ if (setComponentNameForConfirmDeviceCredentialActivity)
+ componentNameOverriddenForConfirmDeviceCredentialActivity
+ else null
+ )
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index aae7ff6..fa78f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,9 +16,13 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
@@ -33,10 +37,12 @@
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.EnableFlags
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.Flags.FLAG_BP_TALKBACK
import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
@@ -94,6 +100,7 @@
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
+private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -107,18 +114,23 @@
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var iconProvider: IconProvider
@Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
@Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
@Mock private lateinit var activityTaskManager: ActivityTaskManager
+ @Mock private lateinit var activityInfo: ActivityInfo
+ @Mock private lateinit var runningTaskInfo: RunningTaskInfo
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
private val logoResFromApp = R.drawable.ic_cake
private val logoFromApp = context.getDrawable(logoResFromApp)
private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
private val defaultLogoDescription = "Test Android App"
private val logoDescriptionFromApp = "Test Cake App"
+ private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -174,9 +186,7 @@
biometricStatusRepository,
fingerprintRepository
)
- selector =
- PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
- selector.resetPrompt()
+
promptContentView =
PromptVerticalListContentView.Builder()
.addListItem(PromptContentItemBulletedText("content item 1"))
@@ -189,29 +199,32 @@
.setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
.build()
- viewModel =
- PromptViewModel(
- displayStateInteractor,
- selector,
- mContext,
- udfpsOverlayInteractor,
- biometricStatusInteractor,
- udfpsUtils
- )
- iconViewModel = viewModel.iconViewModel
-
- // Set up default logo icon and app customized icon
+ // Set up default logo info and app customized info
whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
.thenReturn(applicationInfoNoIcon)
whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
.thenReturn(applicationInfoWithIcon)
+ whenever(packageManager.getApplicationInfo(eq(packageNameForLogoWithOverrides), anyInt()))
+ .thenReturn(applicationInfoWithIcon)
+ whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), anyInt()))
+ .thenThrow(NameNotFoundException())
+
+ whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
+ whenever(iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
whenever(packageManager.getApplicationIcon(applicationInfoWithIcon))
.thenReturn(defaultLogoIcon)
whenever(packageManager.getApplicationLabel(applicationInfoWithIcon))
.thenReturn(defaultLogoDescription)
+ whenever(packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
+ whenever(packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }
+
context.setMockPackageManager(packageManager)
val resources = context.getOrCreateTestableResources()
resources.addOverride(logoResFromApp, logoFromApp)
+ resources.addOverride(
+ R.array.biometric_dialog_package_names_for_logo_with_overrides,
+ arrayOf(packageNameForLogoWithOverrides)
+ )
}
@Test
@@ -1260,8 +1273,8 @@
}
@Test
+ @EnableFlags(FLAG_BP_TALKBACK)
fun hint_for_talkback_guidance() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK)
val hint by collectLastValue(viewModel.accessibilityHint)
// Touches should fall outside of sensor area
@@ -1283,10 +1296,9 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByVerticalListContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1295,13 +1307,12 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
runGenericTest(
contentView = promptContentViewWithMoreOptionsButton,
description = "test description"
) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1310,10 +1321,9 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1322,62 +1332,84 @@
}
@Test
- fun logoIsNullIfPackageNameNotFound() =
- runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_nullIfPkgNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@Test
- fun defaultLogoIfNoLogoSet() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_defaultWithOverrides() =
+ runGenericTest(packageName = packageNameForLogoWithOverrides) {
+ val logo by collectLastValue(viewModel.logo)
+
+ // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
+ // applicationInfoWithIcon with defaultLogoIcon,
+ // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore
+ // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
+ assertThat(logo).isEqualTo(defaultLogoIconWithOverrides)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_defaultIsNull() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_default() = runGenericTest {
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@Test
- fun logoResSetByApp() =
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@Test
- fun logoBitmapSetByApp() =
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@Test
- fun logoDescriptionIsEmptyIfPackageNameNotFound() =
- runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_emptyIfPkgNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@Test
- fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_defaultIsEmpty() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+ val logoDescription by collectLastValue(viewModel.logoDescription)
+ assertThat(logoDescription).isEqualTo("")
+ }
+
+ @Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_default() = runGenericTest {
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@Test
- fun logoDescriptionSetByApp() =
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
@@ -1420,6 +1452,26 @@
packageName: String = OP_PACKAGE_NAME,
block: suspend TestScope.() -> Unit,
) {
+ val topActivity = ComponentName(packageName, "test app")
+ runningTaskInfo.topActivity = topActivity
+ whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo))
+ selector =
+ PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
+ selector.resetPrompt()
+
+ viewModel =
+ PromptViewModel(
+ displayStateInteractor,
+ selector,
+ mContext,
+ udfpsOverlayInteractor,
+ biometricStatusInteractor,
+ udfpsUtils,
+ iconProvider,
+ activityTaskManager
+ )
+ iconViewModel = viewModel.iconViewModel
+
selector.initializePrompt(
requireConfirmation = testCase.confirmationRequested,
allowCredentialFallback = allowCredentialFallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index a569cee..49f2043 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -21,7 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -92,7 +92,7 @@
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
mBroadcastDialogDelegate = new BroadcastDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 62c98b0..7215619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -50,7 +50,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
@@ -104,7 +104,7 @@
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
mBluetoothTileDialogDelegate =
BluetoothTileDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index f62a55d..11f74c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -63,6 +64,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -113,7 +115,6 @@
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
scheduler = TestCoroutineScheduler()
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index bc6c459..5361cef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,9 +35,13 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -50,6 +54,7 @@
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.FakeSystemClock;
+import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.flow.StateFlowKt;
import org.junit.Before;
@@ -89,6 +94,14 @@
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private CommunalInteractor mCommunalInteractor;
+ @Mock
+ private DeviceEntryInteractor mDeviceEntryInteractor;
+ private final MutableStateFlow<Boolean> mIsDeviceEntered =
+ StateFlowKt.MutableStateFlow(false);
+ @Mock
+ private SceneContainerOcclusionInteractor mSceneContainerOcclusionInteractor;
+ private final MutableStateFlow<Boolean> mIsInvisibleDueToOcclusion =
+ StateFlowKt.MutableStateFlow(false);
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -99,15 +112,21 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
+ when(mDeviceEntryInteractor.isDeviceEntered()).thenReturn(mIsDeviceEntered);
+ when(mSceneContainerOcclusionInteractor.getInvisibleDueToOcclusion()).thenReturn(
+ mIsInvisibleDueToOcclusion);
+
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
mStatusBarStateController, mKeyguardStateController,
() -> mShadeInteractor, mBatteryController,
mDockManager, mFakeExecutor,
mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor,
- () -> mCommunalInteractor
+ () -> mCommunalInteractor, () -> mDeviceEntryInteractor,
+ () -> mSceneContainerOcclusionInteractor
);
mFalsingCollector.init();
}
@@ -189,7 +208,8 @@
}
@Test
- public void testRegisterSensor_OccludingActivity() {
+ @DisableSceneContainer
+ public void testRegisterSensor_OccludingActivity_sceneContainerDisabled() {
when(mKeyguardStateController.isOccluded()).thenReturn(true);
ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
@@ -203,6 +223,21 @@
}
@Test
+ @EnableSceneContainer
+ public void testRegisterSensor_OccludingActivity_sceneContainerEnabled() {
+ mIsInvisibleDueToOcclusion.setValue(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughEnterKeyEvent() {
KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
0, 0, 0, 0, 0, 0, 0, "");
@@ -280,7 +315,8 @@
}
@Test
- public void testAvoidUnlocked() {
+ @DisableSceneContainer
+ public void testAvoidUnlocked_sceneContainerDisabled() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
@@ -296,6 +332,23 @@
}
@Test
+ @EnableSceneContainer
+ public void testAvoidUnlocked_sceneContainerEnabled() {
+ MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mIsDeviceEntered.setValue(true);
+
+ // Nothing passed initially
+ mFalsingCollector.onTouchEvent(down);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+ // Up event would normally flush the up event, but doesn't.
+ mFalsingCollector.onTouchEvent(up);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+ }
+
+ @Test
public void testGestureWhenDozing() {
// We check the FalsingManager for taps during the transition to AoD (dozing=true,
// pulsing=false), so the FalsingCollector needs to continue to analyze events that occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 8653308..44a8904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -47,7 +49,7 @@
private val testScope = kosmos.testScope
private val testHelper = kosmos.shortcutHelperTestHelper
-
+ private val sysUiState = kosmos.sysUiState
private val viewModel = kosmos.shortcutHelperViewModel
@Test
@@ -90,12 +92,12 @@
}
@Test
- fun shouldShow_falseAfterViewDestroyed() =
+ fun shouldShow_falseAfterViewClosed() =
testScope.runTest {
val shouldShow by collectLastValue(viewModel.shouldShow)
testHelper.toggle(deviceId = 567)
- viewModel.onUserLeave()
+ viewModel.onViewClosed()
assertThat(shouldShow).isFalse()
}
@@ -108,7 +110,7 @@
testHelper.hideForSystem()
testHelper.toggle(deviceId = 987)
testHelper.showFromActivity()
- viewModel.onUserLeave()
+ viewModel.onViewClosed()
testHelper.hideFromActivity()
testHelper.hideForSystem()
testHelper.toggle(deviceId = 456)
@@ -127,4 +129,27 @@
val shouldShowNew by collectLastValue(viewModel.shouldShow)
assertThat(shouldShowNew).isEqualTo(shouldShow)
}
+
+ @Test
+ fun sysUiStateFlag_disabledByDefault() =
+ testScope.runTest {
+ assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse()
+ }
+
+ @Test
+ fun sysUiStateFlag_trueAfterViewOpened() =
+ testScope.runTest {
+ viewModel.onViewOpened()
+
+ assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isTrue()
+ }
+
+ @Test
+ fun sysUiStateFlag_falseAfterViewClosed() =
+ testScope.runTest {
+ viewModel.onViewOpened()
+ viewModel.onViewClosed()
+
+ assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b50d248..977116e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -8,6 +8,8 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -19,6 +21,10 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
@@ -69,12 +75,13 @@
resourceTrimmer =
ResourceTrimmer(
keyguardInteractor,
- powerInteractor,
- kosmos.keyguardTransitionInteractor,
- globalWindowManager,
- testScope.backgroundScope,
- kosmos.testDispatcher,
- featureFlags
+ powerInteractor = powerInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ globalWindowManager = globalWindowManager,
+ applicationScope = testScope.backgroundScope,
+ bgDispatcher = kosmos.testDispatcher,
+ featureFlags = featureFlags,
+ sceneInteractor = kosmos.sceneInteractor,
)
resourceTrimmer.start()
}
@@ -203,6 +210,7 @@
@Test
@EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @DisableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
@@ -218,6 +226,20 @@
@Test
@EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @EnableSceneContainer
+ fun keyguardTransitionsToGone_trimsFontCache_scene_container() =
+ testScope.runTest {
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+ verifyNoMoreInteractions(globalWindowManager)
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @DisableSceneContainer
fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
testScope.runTest {
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
@@ -231,4 +253,18 @@
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
verify(globalWindowManager, times(0)).trimCaches(any())
}
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @EnableSceneContainer
+ fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() =
+ testScope.runTest {
+ featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ // Memory hidden should still be called.
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(0)).trimCaches(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 62855d7..974e3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,12 +21,18 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,10 +71,11 @@
underTest =
KeyguardDismissActionInteractor(
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- dismissInteractorWithDependencies.interactor,
- testScope.backgroundScope,
+ repository = keyguardRepository,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
+ dismissInteractor = dismissInteractorWithDependencies.interactor,
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = kosmos.sceneInteractor,
)
}
@@ -153,6 +160,7 @@
}
@Test
+ @DisableSceneContainer
fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
testScope.runTest {
val executeDismissAction by collectLastValue(underTest.executeDismissAction)
@@ -179,6 +187,29 @@
}
@Test
+ @EnableSceneContainer
+ fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() =
+ testScope.runTest {
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+ // WHEN a keyguard action will run after the keyguard is gone
+ val onDismissAction = {}
+ keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = onDismissAction,
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(executeDismissAction).isNull()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertThat(executeDismissAction).isNotNull()
+ }
+
+ @Test
fun resetDismissAction() =
testScope.runTest {
val resetDismissAction by collectLastValue(underTest.resetDismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ef15d21..00f94a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -91,33 +91,40 @@
}
private val testScope = kosmos.testScope
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
private var commandQueue = kosmos.fakeCommandQueue
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
- private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
- private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
- private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
- private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
- private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
- private val fromAlternateBouncerTransitionInteractor =
+ private val fromLockscreenTransitionInteractor by lazy {
+ kosmos.fromLockscreenTransitionInteractor
+ }
+ private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor }
+ private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor }
+ private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
+ private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+ private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor }
+ private val fromAlternateBouncerTransitionInteractor by lazy {
kosmos.fromAlternateBouncerTransitionInteractor
- private val fromPrimaryBouncerTransitionInteractor =
+ }
+ private val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
- private val fromDreamingLockscreenHostedTransitionInteractor =
+ }
+ private val fromDreamingLockscreenHostedTransitionInteractor by lazy {
kosmos.fromDreamingLockscreenHostedTransitionInteractor
- private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
+ }
+ private val fromGlanceableHubTransitionInteractor by lazy {
+ kosmos.fromGlanceableHubTransitionInteractor
+ }
- private val powerInteractor = kosmos.powerInteractor
- private val communalInteractor = kosmos.communalInteractor
- private val dockManager = kosmos.fakeDockManager
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val communalInteractor by lazy { kosmos.communalInteractor }
+ private val dockManager by lazy { kosmos.fakeDockManager }
companion object {
@JvmStatic
@@ -633,7 +640,7 @@
// GIVEN a prior transition has run to DREAMING
keyguardRepository.setDreamingWithOverlay(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
- runCurrent()
+ advanceTimeBy(60L)
// WHEN the device wakes up without a keyguard
keyguardRepository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
new file mode 100644
index 0000000..8a5af09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -0,0 +1,1318 @@
+/*
+ * 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.scenetransition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+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.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.lockscreenSceneTransitionInteractor
+
+ private val progress = MutableStateFlow(0f)
+
+ private val sceneTransitions =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+
+ private val lsToGone =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ private val goneToLs =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+ testScope.launch {
+ kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff(
+ KeyguardState.LOCKSCREEN
+ )
+ }
+ }
+
+ /** STL: Ls -> Gone, then settle with Idle(Gone). This is the default case. */
+ @Test
+ fun transition_from_ls_scene_end_in_gone() =
+ testScope.runTest {
+ sceneTransitions.value = lsToGone
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the
+ * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f.
+ */
+ @Test
+ fun transition_from_ls_scene_end_in_ls() =
+ testScope.runTest {
+ sceneTransitions.value = lsToGone
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then settle with Idle(Ls). KTF starts in AOD and needs to inverse correctly
+ * back to AOD.
+ */
+ @Test
+ fun transition_from_ls_scene_on_aod_end_in_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+ sceneTransitions.value = lsToGone
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then settle with Idle(Ls). This is the default case in the reverse
+ * direction.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /** STL: Gone -> Ls (AOD), will transition to AOD once */
+ @Test
+ fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() =
+ testScope.runTest {
+ underTest.onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD)
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then settle with Idle(Gone). KTF in this scenario needs to invert the
+ * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_from_scene() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+ val stepM3 = allSteps[allSteps.size - 3]
+ val stepM2 = allSteps[allSteps.size - 2]
+
+ assertTransition(
+ step = stepM3,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = stepM2,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Shade -> Ls. KTF in this scenario needs to invert the
+ * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f. Then restart a different transition UNDEFINED -> Ls.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Ls -> Shade. This is like continuing the transition from
+ * Ls before the transition before has properly settled. This can happen in STL e.g. with an
+ * accelerated swipe (quick successive fling gestures).
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Gone -> Shade. This is going back to Gone but starting a
+ * transition from Gone before settling in Gone. KTF needs to make sure the transition is
+ * properly inversed and settled in UNDEFINED.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_other_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl still finishes in Ls. After a KTF
+ * transition is started (UNDEFINED -> LOCKSCREEN) KTF immediately considers the active scene to
+ * be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active and may start a new
+ * transition LOCKSCREEN -> *. Here we test LS -> AOD.
+ *
+ * KTF is allowed to already start and play the other transition, while the STL transition may
+ * finish later (gesture completes much later). When we eventually settle the STL transition in
+ * Ls we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for
+ * this scenario the settle can be ignored.
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ // Scene progress should not affect KTF transition anymore
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ // Scene transition still finishes but should not impact KTF transition
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl finishes in Gone.
+ *
+ * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+ *
+ * This is similar to the previous scenario but the gesture may have gone back to its origin. In
+ * this case we can not ignore the settlement, because whatever KTF has done in the meantime it
+ * needs to immediately finish in UNDEFINED (there is a jump cut).
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_gone() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertThat(currentStep?.value).isEqualTo(0f)
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Gone -> Shade
+ *
+ * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+ *
+ * This is similar to the previous scenario but the gesture may have been interrupted by any
+ * other transition. KTF needs to immediately finish in UNDEFINED (there is a jump cut).
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_other_transition() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Ls -> Shade
+ *
+ * In this scenario it is important that the last STL transition Ls -> Shade triggers a cancel
+ * of the * -> AOD transition but then also properly starts a transition AOD (not LOCKSCREEN) ->
+ * UNDEFINED transition.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+ allSteps[allSteps.size - 3]
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Shade -> Ls
+ *
+ * In this scenario it is important KTF is brought back into a FINISHED UNDEFINED state
+ * considering the state is already on AOD from where a new UNDEFINED -> LOCKSCREEN transition
+ * can be started.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.7f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt multiple canceled KTF transitions, then STL Ls -> Shade
+ *
+ * Similar to
+ * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+ * but here KTF is canceled multiple times such that in the end OCCLUDED -> UNDEFINED is
+ * properly started. (not from AOD or LOCKSCREEN)
+ *
+ * Note: there is no test which tests multiple cancels from the STL side, this is because all
+ * STL transitions trigger a response from LockscreenSceneTransitionInteractor which forces KTF
+ * into a specific state, so testing each pair is enough. Meanwhile KTF can move around without
+ * any reaction from LockscreenSceneTransitionInteractor.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_cancel_sequence_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.AOD,
+ to = KeyguardState.DOZING,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.DOZING,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by KTF LS -> AOD which is FINISHED before STL Ls -> Shade
+ *
+ * Similar to
+ * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+ * but here KTF is finishing the transition and only then gets interrupted. Should correctly
+ * start AOD -> UNDEFINED.
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_and_finished_by_ktf_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ val ktfUuid =
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.updateTransition(
+ ktfUuid!!,
+ 1f,
+ TransitionState.FINISHED
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Ls -> Bouncer. This happens when the next transition is
+ * immediately started from Gone without settling in Idle. This specifically happens when
+ * dragging down on Ls and then changing direction. The transition will switch from -> Shade to
+ * -> Bouncer without settling or signaling any cancellation as STL considers this to be the
+ * same gesture.
+ *
+ * In STL there is no guarantee that transitions settle in Idle before continuing.
+ */
+ @Test
+ fun transition_from_ls_scene_interrupted_by_other_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Bouncer,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Gone -> Ls. This happens when the next transition is
+ * immediately started from Gone without settling in Idle. In STL there is no guarantee that
+ * transitions settle in Idle before continuing.
+ */
+ @Test
+ fun transition_from_ls_scene_interrupted_by_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Gone -> Bouncer. This happens when the next transition
+ * is immediately started from Gone without settling in Idle. In STL there is no guarantee that
+ * transitions settle in Idle before continuing.
+ */
+ @Test
+ fun transition_from_ls_scene_interrupted_by_other_stl_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Bouncer,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ private fun assertTransition(
+ step: TransitionStep,
+ from: KeyguardState? = null,
+ to: KeyguardState? = null,
+ state: TransitionState? = null,
+ progress: Float? = null
+ ) {
+ if (from != null) assertThat(step.from).isEqualTo(from)
+ if (to != null) assertThat(step.to).isEqualTo(to)
+ if (state != null) assertThat(step.transitionState).isEqualTo(state)
+ if (progress != null) assertThat(step.value).isEqualTo(progress)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9b2db3e..1f13298 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -41,10 +42,9 @@
private lateinit var executor: FakeExecutor
@Mock private lateinit var activityTaskManagerService: IActivityTaskManager
-
@Mock private lateinit var keyguardStateController: KeyguardStateController
-
@Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Before
fun setUp() {
@@ -57,6 +57,7 @@
activityTaskManagerService = activityTaskManagerService,
keyguardStateController = keyguardStateController,
keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 1396b20..391831a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -18,15 +18,19 @@
package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
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.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +38,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
@RunWith(JUnit4::class)
@@ -49,13 +54,35 @@
fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() =
testScope.runTest {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
+ givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
+ stepToLockscreen(0f, TransitionState.STARTED),
+ stepToLockscreen(.4f),
+ stepToLockscreen(1f, TransitionState.FINISHED),
+ ),
+ testScope,
+ )
+ assertThat(canShowAlternateBouncer).isTrue()
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromLockscreenToAlternateBouncer(.4f),
+ stepFromLockscreenToAlternateBouncer(.6f),
+ ),
+ testScope,
+ )
+ assertThat(canShowAlternateBouncer).isTrue()
+ assertThat(alternateBouncerWindowRequired).isTrue()
+
+ transitionRepository.sendTransitionSteps(
+ listOf(
stepFromAlternateBouncer(0f, TransitionState.STARTED),
- stepFromAlternateBouncer(.4f),
+ stepFromAlternateBouncer(.2f),
stepFromAlternateBouncer(.6f),
),
testScope,
@@ -77,13 +104,21 @@
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
+ givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
- stepFromAlternateBouncer(0f, TransitionState.STARTED),
- stepFromAlternateBouncer(.4f),
- stepFromAlternateBouncer(.6f),
- stepFromAlternateBouncer(1f),
+ stepToLockscreen(0f, TransitionState.STARTED),
+ stepToLockscreen(.4f),
+ stepToLockscreen(1f, TransitionState.FINISHED),
+ ),
+ testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromLockscreenToAlternateBouncer(.4f),
+ stepFromLockscreenToAlternateBouncer(.6f),
),
testScope,
)
@@ -96,13 +131,23 @@
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
+ givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsUdfps()
transitionRepository.sendTransitionSteps(
listOf(
+ stepFromLockscreenToDozing(0f, TransitionState.STARTED),
+ stepFromLockscreenToDozing(.4f),
+ stepFromLockscreenToDozing(.6f),
+ stepFromLockscreenToDozing(1f, TransitionState.FINISHED),
+ ),
+ testScope,
+ )
+ assertThat(alternateBouncerWindowRequired).isFalse()
+ transitionRepository.sendTransitionSteps(
+ listOf(
stepFromDozingToLockscreen(0f, TransitionState.STARTED),
stepFromDozingToLockscreen(.4f),
stepFromDozingToLockscreen(.6f),
- stepFromDozingToLockscreen(1f),
),
testScope,
)
@@ -115,19 +160,39 @@
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val alternateBouncerWindowRequired by
collectLastValue(underTest.alternateBouncerWindowRequired)
+ givenCanShowAlternateBouncer()
fingerprintPropertyRepository.supportsRearFps()
transitionRepository.sendTransitionSteps(
listOf(
- stepFromAlternateBouncer(0f, TransitionState.STARTED),
- stepFromAlternateBouncer(.4f),
- stepFromAlternateBouncer(.6f),
- stepFromAlternateBouncer(1f),
+ stepToLockscreen(0f, TransitionState.STARTED),
+ stepToLockscreen(.4f),
+ stepToLockscreen(1f, TransitionState.FINISHED),
+ ),
+ testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+ stepFromLockscreenToAlternateBouncer(.4f),
+ stepFromLockscreenToAlternateBouncer(.6f),
),
testScope,
)
assertThat(alternateBouncerWindowRequired).isFalse()
}
+ private fun stepToLockscreen(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return step(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ )
+ }
+
private fun stepFromAlternateBouncer(
value: Float,
state: TransitionState = TransitionState.RUNNING
@@ -140,6 +205,18 @@
)
}
+ private fun stepFromLockscreenToAlternateBouncer(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return step(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = value,
+ transitionState = state,
+ )
+ }
+
private fun stepFromDozingToLockscreen(
value: Float,
state: TransitionState = TransitionState.RUNNING
@@ -152,6 +229,18 @@
)
}
+ private fun stepFromLockscreenToDozing(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return step(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ value = value,
+ transitionState = state,
+ )
+ }
+
private fun step(
from: KeyguardState,
to: KeyguardState,
@@ -166,4 +255,16 @@
ownerName = "AlternateBouncerViewModelTest"
)
}
+
+ /**
+ * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside
+ * from the fingerprint modality.
+ */
+ private fun givenCanShowAlternateBouncer() {
+ kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ }
}
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 768d446..40663ce 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
@@ -23,6 +23,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -56,7 +57,7 @@
class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
- val underTest = kosmos.keyguardClockViewModel
+ val underTest by lazy { kosmos.keyguardClockViewModel }
val res = context.resources
@Mock lateinit var clockController: ClockController
@@ -96,6 +97,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -110,6 +112,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -124,6 +127,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_singleShade_smallClock_smallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -193,6 +197,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun testClockSize_dynamicClockSize() =
testScope.runTest {
with(kosmos) {
@@ -216,6 +221,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun isLargeClockVisible_whenSmallClockSize_isFalse() =
testScope.runTest {
val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
index e56a253..5986f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
@@ -33,9 +33,6 @@
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -50,6 +47,9 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
@@ -172,20 +172,20 @@
fun testOnRemovedForCurrent_callsListener() {
// GIVEN a media was removed for main user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should tell the listener
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
fun testOnRemovedForGuest_doesNotCallListener() {
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -197,7 +197,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -230,7 +230,7 @@
setPrivateProfileUnavailable()
// THEN we should add the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
}
@Test
@@ -360,7 +360,7 @@
@Test
fun testOnNotificationRemoved_doesntHaveMedia() {
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 5a2d22d..3372f06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -66,9 +66,6 @@
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -90,6 +87,9 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
import org.mockito.quality.Strictness
private const val KEY = "KEY"
@@ -346,7 +346,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -532,7 +532,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -777,7 +777,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
// WHEN the second is removed
mediaDataManager.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -791,7 +791,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -816,7 +816,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -866,7 +866,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -905,7 +905,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -915,7 +915,7 @@
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1148,7 +1148,7 @@
mediaDataManager.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1156,19 +1156,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2077,7 +2077,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2093,7 +2093,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2146,7 +2146,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2199,7 +2199,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2253,7 +2253,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2279,7 +2279,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2329,7 +2329,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index bb5b572..dd05a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -202,24 +202,24 @@
@Test
public void mediaDataRemoved() {
// WHEN media data is removed without first receiving device or data
- mManager.onMediaDataRemoved(KEY);
+ mManager.onMediaDataRemoved(KEY, false);
// THEN a removed event isn't emitted
- verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+ verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean());
}
@Test
public void mediaDataRemovedAfterMediaEvent() {
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
public void mediaDataRemovedAfterDeviceEvent() {
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 77ad263..caaa42f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -40,9 +40,6 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -60,6 +57,9 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
@@ -205,9 +205,9 @@
assertThat(currentMedia).containsExactly(mediaCommonModel)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).doesNotContain(mediaCommonModel)
}
@@ -218,9 +218,9 @@
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -239,7 +239,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -291,7 +291,7 @@
val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
// THEN we should remove the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
assertThat(currentMedia)
.containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
}
@@ -502,7 +502,7 @@
val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
.isFalse()
assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 1de7ee3..3bf4173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -71,10 +71,6 @@
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.os.FakeHandler
@@ -101,6 +97,10 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
private const val KEY = "KEY"
@@ -384,7 +384,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -567,7 +567,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -812,7 +812,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
// WHEN the second is removed
mediaDataProcessor.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -826,7 +826,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -851,7 +851,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -901,7 +901,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -940,7 +940,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -950,7 +950,7 @@
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1183,7 +1183,7 @@
mediaDataProcessor.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1191,19 +1191,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2102,7 +2102,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2118,7 +2118,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2171,7 +2171,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2224,7 +2224,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2278,7 +2278,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2304,7 +2304,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2354,7 +2354,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index a447e44..d2701dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -51,7 +51,6 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -60,6 +59,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -71,6 +71,7 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.eq
private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
@@ -158,7 +159,8 @@
@Test
fun removeUnknown() {
- manager.onMediaDataRemoved("unknown")
+ manager.onMediaDataRemoved("unknown", false)
+ verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -170,7 +172,7 @@
@Test
fun loadAndRemoveMediaData() {
manager.onMediaDataLoaded(KEY, null, mediaData)
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, false)
fakeBgExecutor.runAllReady()
verify(lmm).unregisterCallback(any())
verify(muteAwaitManager).stopListening()
@@ -386,9 +388,9 @@
fun listenerReceivesKeyRemoved() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// WHEN the notification is removed
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, true)
// THEN the listener receives key removed event
- verify(listener).onKeyRemoved(eq(KEY))
+ verify(listener).onKeyRemoved(eq(KEY), eq(true))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 5a3c220..31a2435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -27,7 +27,6 @@
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
import org.junit.Before
@@ -38,12 +37,13 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.any
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
private const val PACKAGE = "PKG"
private const val KEY = "TEST_KEY"
@@ -165,10 +165,10 @@
@Test
fun noMediaSession_removedEventNotFiltered() {
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -193,11 +193,11 @@
whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
sessionListener.onActiveSessionsChanged(controllers)
// WHEN a removed event is received
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -294,7 +294,7 @@
anyBoolean()
)
// AND there should be a removed event for key2
- verify(mediaListener).onMediaDataRemoved(eq(key2))
+ verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 3cc65c9..6ca0bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -31,10 +31,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -52,6 +48,10 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
private const val KEY = "KEY"
private const val PACKAGE = "PKG"
@@ -166,12 +166,12 @@
@Test
fun testOnMediaDataRemoved_unregistersPlaybackListener() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController).unregisterCallback(anyObject())
// Ignores duplicate requests
clearInvocations(mediaController)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController, never()).unregisterCallback(anyObject())
}
@@ -181,7 +181,7 @@
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
// WHEN the media is removed
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -398,7 +398,7 @@
// WHEN we have a resume control
testOnMediaDataLoaded_resumption_registersTimeout()
// AND the media is removed
- mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 0a5aace..7856f9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -33,6 +33,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -52,15 +56,16 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
@@ -74,12 +79,14 @@
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -88,6 +95,9 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
private val DATA = MediaTestUtils.emptyMediaData
@@ -136,6 +146,9 @@
private lateinit var testDispatcher: TestDispatcher
private lateinit var mediaCarouselController: MediaCarouselController
+ private var originalResumeSetting =
+ Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -145,29 +158,30 @@
testDispatcher = UnconfinedTestDispatcher()
mediaCarouselController =
MediaCarouselController(
- context,
- mediaControlPanelFactory,
- visualStabilityProvider,
- mediaHostStatesManager,
- activityStarter,
- clock,
- kosmos.testDispatcher,
- executor,
- bgExecutor,
- testDispatcher,
- mediaDataManager,
- configurationController,
- falsingManager,
- dumpManager,
- logger,
- debugLogger,
- mediaFlags,
- keyguardUpdateMonitor,
- kosmos.keyguardTransitionInteractor,
- globalSettings,
- secureSettings,
- kosmos.mediaCarouselViewModel,
- mediaViewControllerFactory,
+ context = context,
+ mediaControlPanelFactory = mediaControlPanelFactory,
+ visualStabilityProvider = visualStabilityProvider,
+ mediaHostStatesManager = mediaHostStatesManager,
+ activityStarter = activityStarter,
+ systemClock = clock,
+ mainDispatcher = kosmos.testDispatcher,
+ executor = executor,
+ bgExecutor = bgExecutor,
+ backgroundDispatcher = testDispatcher,
+ mediaManager = mediaDataManager,
+ configurationController = configurationController,
+ falsingManager = falsingManager,
+ dumpManager = dumpManager,
+ logger = logger,
+ debugLogger = debugLogger,
+ mediaFlags = mediaFlags,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ globalSettings = globalSettings,
+ secureSettings = secureSettings,
+ mediaCarouselViewModel = kosmos.mediaCarouselViewModel,
+ mediaViewControllerFactory = mediaViewControllerFactory,
+ sceneInteractor = kosmos.sceneInteractor,
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
@@ -186,6 +200,15 @@
)
}
+ @After
+ fun tearDown() {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME,
+ originalResumeSetting
+ )
+ }
+
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -818,10 +841,12 @@
verify(mediaCarousel).visibility = View.VISIBLE
}
+ @DisableSceneContainer
@ExperimentalCoroutinesApi
@Test
fun testKeyguardGone_showMediaCarousel() =
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -840,10 +865,30 @@
job.cancel()
}
+ @EnableSceneContainer
+ @ExperimentalCoroutinesApi
+ @Test
+ fun testKeyguardGone_showMediaCarousel_scene_container() =
+ kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+ var updatedVisibility = false
+ mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
+ mediaCarouselController.mediaCarousel = mediaCarousel
+
+ val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ verify(mediaCarousel).visibility = View.VISIBLE
+ assertEquals(true, updatedVisibility)
+
+ job.cancel()
+ }
+
@ExperimentalCoroutinesApi
@Test
fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -870,6 +915,7 @@
@Test
fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -968,6 +1014,45 @@
verify(panel).updateAnimatorDurationScale()
}
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+
+ // This is processed as a user-initiated dismissal
+ verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+ mediaCarouselController.updateHostVisibility = {}
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can't be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ visualStabilityCallback.value.onReorderingAllowed()
+
+ // This is processed as a user-initiated dismissal
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
/**
* Helper method when a configuration change occurs.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 83e4d31..6d7976e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -98,11 +98,6 @@
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -125,6 +120,9 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
private const val KEY = "TEST_KEY"
private const val PACKAGE = "PKG"
@@ -247,8 +245,7 @@
// Set up package manager mocks
val icon = context.getDrawable(R.drawable.ic_android)
whenever(packageManager.getApplicationIcon(anyString())).thenReturn(icon)
- whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
- .thenReturn(icon)
+ whenever(packageManager.getApplicationIcon(any<ApplicationInfo>())).thenReturn(icon)
whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
.thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
@@ -644,7 +641,7 @@
bgExecutor.runAllReady()
mainExecutor.runAllReady()
- verify(albumView).setImageDrawable(any(Drawable::class.java))
+ verify(albumView).setImageDrawable(any<Drawable>())
}
@Test
@@ -657,7 +654,7 @@
bgExecutor.runAllReady()
mainExecutor.runAllReady()
- verify(albumView).setImageDrawable(any(Drawable::class.java))
+ verify(albumView).setImageDrawable(any<Drawable>())
}
@Test
@@ -675,12 +672,12 @@
player.bindPlayer(state0, PACKAGE)
bgExecutor.runAllReady()
mainExecutor.runAllReady()
- verify(albumView).setImageDrawable(any(Drawable::class.java))
+ verify(albumView).setImageDrawable(any<Drawable>())
// Run Metadata update so that later states don't update
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator, times(2)).addListener(captor.capture())
- captor.value.onAnimationEnd(mockAnimator)
+ captor.lastValue.onAnimationEnd(mockAnimator)
assertThat(titleText.getText()).isEqualTo(TITLE)
assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -696,13 +693,13 @@
player.bindPlayer(state2, PACKAGE)
bgExecutor.runAllReady()
mainExecutor.runAllReady()
- verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java))
+ verify(albumView, times(2)).setImageDrawable(any<Drawable>())
// Fourth binding to new image runs transition due to color scheme change
player.bindPlayer(state3, PACKAGE)
bgExecutor.runAllReady()
mainExecutor.runAllReady()
- verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java))
+ verify(albumView, times(3)).setImageDrawable(any<Drawable>())
}
@Test
@@ -974,7 +971,7 @@
val captor = argumentCaptor<SeekBarObserver>()
verify(seekBarData).observeForever(captor.capture())
- val seekBarObserver = captor.value!!
+ val seekBarObserver = captor.lastValue
// Then the seekbar is set to animate
assertThat(seekBarObserver.animationEnabled).isTrue()
@@ -1086,27 +1083,19 @@
whenever(mockAvd0.isRunning()).thenReturn(false)
val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java)
verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture())
- verify(mockAvd1, never())
- .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd2, never())
- .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+ verify(mockAvd1, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd2, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>())
captor.getValue().onAnimationEnd(mockAvd0)
// Validate correct state was bound
assertThat(actionPlayPause.contentDescription).isEqualTo("loading")
assertThat(actionPlayPause.getBackground()).isNull()
- verify(mockAvd0, times(1))
- .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd1, times(1))
- .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd2, times(1))
- .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd0, times(1))
- .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd1, times(1))
- .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
- verify(mockAvd2, never())
- .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+ verify(mockAvd0, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd1, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd2, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd0, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd1, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
+ verify(mockAvd2, never()).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
}
@Test
@@ -1118,7 +1107,7 @@
// Capture animation handler
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator, times(2)).addListener(captor.capture())
- val handler = captor.value
+ val handler = captor.lastValue
// Validate text views unchanged but animation started
assertThat(titleText.getText()).isEqualTo("")
@@ -1147,7 +1136,7 @@
// Capture animation handler
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator, times(2)).addListener(captor.capture())
- val handler = captor.value
+ val handler = captor.lastValue
// Validate text views unchanged but animation started
assertThat(titleText.getText()).isEqualTo("")
@@ -1179,7 +1168,7 @@
// Capture animation handler
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator, times(2)).addListener(captor.capture())
- val handler = captor.value
+ val handler = captor.lastValue
handler.onAnimationEnd(mockAnimator)
assertThat(artistText.getText()).isEqualTo("ARTIST_0")
@@ -1344,7 +1333,7 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
}
@Test
@@ -1360,7 +1349,8 @@
@Test
fun player_dismissButtonClick_notInManager() {
val mediaKey = "key for dismissal"
- whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
+ whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true)))
+ .thenReturn(false)
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
@@ -1369,8 +1359,8 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
- verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
+ verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true))
}
@Test
@@ -1774,10 +1764,9 @@
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
- val callback: () -> Unit = {}
- val captor = KotlinArgumentCaptor(callback::class.java)
+ val captor = argumentCaptor<() -> Unit>()
verify(seekBarViewModel).logSeek = captor.capture()
- captor.value.invoke()
+ captor.lastValue.invoke()
verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId))
}
@@ -1800,7 +1789,7 @@
// THEN it sends the PendingIntent without dismissing keyguard first,
// and does not use the Intent directly (see b/271845008)
captor.value.onClick(viewHolder.player)
- verify(pendingIntent).send(any(Bundle::class.java))
+ verify(pendingIntent).send(any<Bundle>())
verify(pendingIntent, never()).getIntent()
verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
}
@@ -2218,8 +2207,8 @@
mainExecutor.runAllReady()
verify(recCardTitle).setTextColor(any<Int>())
- verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
- verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>())
+ verify(coverItem, times(3)).setImageDrawable(any<Drawable>())
verify(coverItem, times(3)).imageMatrix = any()
}
@@ -2546,7 +2535,7 @@
seamless.callOnClick()
// Then we send the pending intent as is, without modifying the original intent
- verify(pendingIntent).send(any(Bundle::class.java))
+ verify(pendingIntent).send(any<Bundle>())
verify(pendingIntent, never()).getIntent()
}
@@ -2578,13 +2567,16 @@
return Icon.createWithBitmap(bmp)
}
- private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
- withArgCaptor {
- verify(seekBarViewModel).setScrubbingChangeListener(capture())
- }
+ private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener {
+ val captor = argumentCaptor<SeekBarViewModel.ScrubbingChangeListener>()
+ verify(seekBarViewModel).setScrubbingChangeListener(captor.capture())
+ return captor.lastValue
+ }
- private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
- verify(seekBarViewModel).setEnabledChangeListener(capture())
+ private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener {
+ val captor = argumentCaptor<SeekBarViewModel.EnabledChangeListener>()
+ verify(seekBarViewModel).setEnabledChangeListener(captor.capture())
+ return captor.lastValue
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 3b6a88a..5dbfe47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -25,6 +25,8 @@
import static org.mockito.Mockito.verify;
import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -91,8 +93,8 @@
}
@Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -105,8 +107,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -119,8 +121,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
@@ -133,8 +135,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index ff7c970..8f8630e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -104,11 +104,11 @@
listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
/* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController, never()).removeComplication(any());
when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
index 8e05410..c06a28e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -42,13 +42,13 @@
fun updateFlags() {
underTest.updateFlags(
Display.DEFAULT_DISPLAY,
- 1 to true,
- 2 to false,
- 3 to true,
+ 1L to true,
+ 2L to false,
+ 3L to true,
)
- assertThat(underTest.flags and 1).isNotEqualTo(0)
- assertThat(underTest.flags and 2).isEqualTo(0)
- assertThat(underTest.flags and 3).isNotEqualTo(0)
+ assertThat(underTest.flags and 1L).isNotEqualTo(0L)
+ assertThat(underTest.flags and 2L).isEqualTo(0L)
+ assertThat(underTest.flags and 3L).isNotEqualTo(0L)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
index 9f0e67b..85cc88d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
@@ -15,11 +15,13 @@
*/
package com.android.systemui.monet
-import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.util.Log
+import android.util.Pair
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.theme.DynamicColors
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor
import com.google.ux.material.libmonet.hct.Hct
import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
import java.io.File
@@ -81,6 +83,10 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ColorSchemeTest : SysuiTestCase() {
+ private val defaultContrast = 0.0
+ private val defaultIsDark = false
+ private val defaultIsFidelity = false
+
@Test
fun generateThemeStyles() {
val document = buildDoc<Any>()
@@ -107,7 +113,7 @@
}
val style = document.createElement(styleValue.name.lowercase())
- val colorScheme = ColorScheme(sourceColor.toInt(), false, styleValue)
+ val colorScheme = ColorScheme(sourceColor.toInt(), defaultIsDark, styleValue)
style.appendChild(
document.createTextNode(
@@ -139,7 +145,7 @@
document.appendWithBreak(resources)
// shade colors
- val colorScheme = ColorScheme(GOOGLE_BLUE, false)
+ val colorScheme = ColorScheme(GOOGLE_BLUE, defaultIsDark)
arrayOf(
Triple("accent1", "Primary", colorScheme.accent1),
Triple("accent2", "Secondary", colorScheme.accent2),
@@ -162,24 +168,35 @@
resources.appendWithBreak(document.createComment(commentRoles), 2)
- // dynamic colors
- arrayOf(false, true).forEach { isDark ->
- val suffix = if (isDark) "_dark" else "_light"
- val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, 0.5)
- DynamicColors.allDynamicColorsMapped(false).forEach {
- resources.createColorEntry(
- "system_${it.first}$suffix",
- it.second.getArgb(dynamicScheme)
- )
+ fun generateDynamic(pairs: List<Pair<String, DynamicColor>>) {
+ arrayOf(false, true).forEach { isDark ->
+ val suffix = if (isDark) "_dark" else "_light"
+ val dynamicScheme =
+ SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, defaultContrast)
+ pairs.forEach {
+ resources.createColorEntry(
+ "system_${it.first}$suffix",
+ it.second.getArgb(dynamicScheme)
+ )
+ }
}
}
+ // dynamic colors
+ generateDynamic(DynamicColors.allDynamicColorsMapped(defaultIsFidelity))
+
// fixed colors
- val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), false, 0.5)
- DynamicColors.getFixedColorsMapped(false).forEach {
+ val dynamicScheme =
+ SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), defaultIsDark, defaultContrast)
+ DynamicColors.getFixedColorsMapped(defaultIsFidelity).forEach {
resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme))
}
+ resources.appendWithBreak(document.createComment(commentRoles), 2)
+
+ // custom colors
+ generateDynamic(DynamicColors.getCustomColorsMapped(defaultIsFidelity))
+
saveFile(document, "role_values.xml")
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 224e755..2ff660f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -125,7 +125,7 @@
private AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityServicesStateChangeListener;
- private static final int ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
+ private static final long ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
private NavBarHelper mNavBarHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 0e7a215..6cea1e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -38,7 +38,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -300,7 +300,7 @@
doNothing().when(mWindowManager).addView(any(), any());
doNothing().when(mWindowManager).removeViewImmediate(any());
mMockSysUiState = mock(SysUiState.class);
- when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+ when(mMockSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mMockSysUiState);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 8d01e80d..bba275e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -16,18 +16,18 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.wm.shell.back.BackAnimation
import com.android.wm.shell.pip.Pip
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
@SmallTest
class TaskbarDelegateTest : SysuiTestCase() {
@@ -74,7 +74,7 @@
`when`(mNavBarHelper.edgeBackGestureHandler).thenReturn(mEdgeBackGestureHandler)
`when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
`when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
- `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
+ `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState)
mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance()
mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory,
mStatusBarKeyguardViewManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 890e1e0..0998c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -94,6 +94,8 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
@@ -1576,17 +1578,19 @@
}
@Test
+ @DisableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreview_flagDisabled() {
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_userLocked() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1594,9 +1598,9 @@
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_userUnlocked() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1605,9 +1609,9 @@
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_doesNotSetTwice() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1617,9 +1621,11 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1628,10 +1634,12 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked()
throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1641,10 +1649,12 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice()
throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
index 629c663..bc947fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -3,6 +3,7 @@
import android.content.ComponentName
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
+import android.widget.Switch
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
@@ -66,15 +67,19 @@
assertThat(proto?.hasBooleanState()).isFalse()
}
+ /**
+ * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the
+ * proto having a booleanState. The value of that boolean is true iff the tile is active.
+ */
@Test
- fun booleanState_ACTIVE() {
+ fun adapterState_ACTIVE() {
val state =
- QSTile.BooleanState().apply {
+ QSTile.AdapterState().apply {
spec = TEST_SPEC
label = TEST_LABEL
secondaryLabel = TEST_SUBTITLE
state = Tile.STATE_ACTIVE
- value = true
+ expandedAccessibilityClassName = Switch::class.java.name
}
val proto = state.toProto()
@@ -89,6 +94,33 @@
assertThat(proto?.booleanState).isTrue()
}
+ /**
+ * Similar to [adapterState_ACTIVE], the use of
+ * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable.
+ */
+ @Test
+ fun adapterState_INACTIVE() {
+ val state =
+ QSTile.AdapterState().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_INACTIVE
+ expandedAccessibilityClassName = Switch::class.java.name
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(proto?.hasBooleanState()).isTrue()
+ assertThat(proto?.booleanState).isFalse()
+ }
+
@Test
fun noSpec_returnsNull() {
val state =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index db752dd..d15cfbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.iconTilesRepository
@@ -48,9 +47,6 @@
data object TestGridLayoutType : GridLayoutType
- private val gridLayout: MutableStateFlow<GridLayoutType> =
- MutableStateFlow(InfiniteGridLayoutType)
-
private val iconOnlyTiles =
MutableStateFlow(
setOf(
@@ -74,17 +70,13 @@
Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
Pair(TestGridLayoutType, noopGridConsistencyInteractor)
)
- gridLayoutTypeRepository =
- object : GridLayoutTypeRepository {
- override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
- }
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@Before
fun setUp() {
- gridLayout.value = InfiniteGridLayoutType
+ with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
underTest.start()
}
@@ -94,7 +86,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayout.value = TestGridLayoutType
+ gridLayoutTypeRepository.setLayout(TestGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index e2a3fac6..ad87315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
+import com.android.internal.telephony.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
@@ -33,10 +33,12 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.Job
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -44,11 +46,15 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class AirplaneModeTileTest : SysuiTestCase() {
+
@Mock
private lateinit var mHost: QSHost
@Mock
@@ -62,7 +68,9 @@
@Mock
private lateinit var mBroadcastDispatcher: BroadcastDispatcher
@Mock
- private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+ private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+ @Mock
+ private lateinit var mConnectivityManager: ConnectivityManager
@Mock
private lateinit var mGlobalSettings: GlobalSettings
@Mock
@@ -72,13 +80,15 @@
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
+ @Mock
+ private lateinit var mClickJob: Job
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mTestableLooper = TestableLooper.get(this)
Mockito.`when`(mHost.context).thenReturn(mContext)
Mockito.`when`(mHost.userContext).thenReturn(mContext)
-
+ Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
mTile = AirplaneModeTile(
mHost,
mUiEventLogger,
@@ -90,7 +100,7 @@
mActivityStarter,
mQsLogger,
mBroadcastDispatcher,
- mConnectivityManager,
+ mLazyConnectivityManager,
mGlobalSettings,
mUserTracker)
}
@@ -120,4 +130,24 @@
assertThat(state.icon)
.isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
}
+
+ @Test
+ fun handleClick_noSatelliteFeature_directSetAirplaneMode() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ mTile.handleClick(null)
+
+ verify(mConnectivityManager).setAirplaneMode(any())
+ }
+
+ @Test
+ fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ Mockito.`when`(mClickJob.isCompleted).thenReturn(false)
+ mTile.mClickJob = mClickJob
+
+ mTile.handleClick(null)
+
+ verify(mConnectivityManager, times(0)).setAirplaneMode(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 830f08a..1ffbb7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,10 +9,11 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.internal.telephony.flags.Flags
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.plugins.ActivityStarter
@@ -23,13 +24,14 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -37,6 +39,7 @@
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -54,7 +57,7 @@
@Mock private lateinit var uiEventLogger: QsEventLogger
@Mock private lateinit var featureFlags: FeatureFlagsClassic
@Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
-
+ @Mock private lateinit var clickJob: Job
private lateinit var testableLooper: TestableLooper
private lateinit var tile: FakeBluetoothTile
@@ -191,6 +194,41 @@
}
@Test
+ fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(false)
+ `when`(clickJob.isCompleted).thenReturn(false)
+ tile.mClickJob = clickJob
+
+ tile.handleClick(null)
+
+ verify(bluetoothController, times(0)).setBluetoothEnabled(any())
+ }
+
+ @Test
+ fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(false)
+
+ tile.handleClick(null)
+
+ verify(bluetoothController).setBluetoothEnabled(any())
+ }
+
+ @Test
+ fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(true)
+
+ tile.handleClick(null)
+
+ verify(bluetoothTileDialogViewModel).showDialog(null)
+ }
+
+ @Test
fun testMetadataListener_whenDisconnected_isUnregistered() {
val state = QSTile.BooleanState()
val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index f88a5a0..b75b318 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -20,7 +20,7 @@
import static junit.framework.Assert.assertNotSame;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -81,7 +81,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
when(mSystemUIDialog.getContext()).thenReturn(mContext);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index effae5f..74deae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -72,7 +72,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.atLeast
import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.intThat
+import org.mockito.Mockito.longThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
@@ -162,7 +162,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
)
}
@@ -172,7 +172,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
)
}
@@ -182,7 +182,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
)
}
@@ -194,7 +194,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 6846c72..fcc6b4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -55,6 +55,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -94,7 +95,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(screenCaptureDisabledDialog)
whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
index 5e7d8fb..a10d81f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -21,7 +21,6 @@
import android.os.Bundle
import android.os.UserHandle
import android.testing.AndroidTestingRunner
-import android.view.View
import android.view.Window
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -49,7 +48,7 @@
private val intentExecutor = mock<ActionIntentExecutor>()
private val window = mock<Window>()
- private val view = mock<View>()
+ private val viewProxy = mock<ScreenshotShelfViewProxy>()
private val onDismiss = mock<(() -> Unit)>()
private val pendingIntent = mock<PendingIntent>()
@@ -70,16 +69,16 @@
}
@Test
- fun sendPendingIntent_dismisses() = runTest {
+ fun sendPendingIntent_requestsDismissal() = runTest {
actionExecutor = createActionExecutor()
actionExecutor.sendPendingIntent(pendingIntent)
verify(pendingIntent).send(any(Bundle::class.java))
- verify(onDismiss).invoke()
+ verify(viewProxy).requestDismissal(null)
}
private fun createActionExecutor(): ActionExecutor {
- return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+ return ActionExecutor(intentExecutor, testScope, window, viewProxy, onDismiss)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 5e53fe1..5cd3f66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -23,17 +23,18 @@
import android.testing.TestableContext
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.proxy.SystemUiProxy
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
@RunWith(AndroidTestingRunner::class)
class ActionIntentExecutorTest : SysuiTestCase() {
@@ -44,8 +45,9 @@
private val testableContext = TestableContext(mContext)
private val activityManagerWrapper = mock<ActivityManagerWrapper>()
+ private val systemUiProxy = mock<SystemUiProxy>()
+
private val displayTracker = mock<DisplayTracker>()
- private val keyguardController = mock<ScreenshotKeyguardController>()
private val actionIntentExecutor =
ActionIntentExecutor(
@@ -53,12 +55,12 @@
activityManagerWrapper,
testScope,
mainDispatcher,
+ systemUiProxy,
displayTracker,
- keyguardController,
)
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
+ @EnableFlags(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
fun launchIntent_callsCloseSystemWindows() =
testScope.runTest {
val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5b47c94..8e32907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -547,6 +547,8 @@
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
// Dreaming->Lockscreen
+ when(mKeyguardTransitionInteractor.transition(any()))
+ .thenReturn(emptyFlow());
when(mKeyguardTransitionInteractor.transition(any(), any()))
.thenReturn(emptyFlow());
when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
@@ -655,7 +657,7 @@
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 45d0102..4a867a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -173,7 +174,7 @@
.thenReturn(keyguardBouncerComponent)
whenever(keyguardBouncerComponent.securityContainerController)
.thenReturn(keyguardSecurityContainerController)
- whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING)))
.thenReturn(emptyFlow<TransitionStep>())
featureFlagsClassic = FakeFeatureFlagsClassic()
@@ -518,46 +519,6 @@
}
@Test
- fun handleExternalTouch_intercepted_sendsOnTouch() {
- // Accept dispatch and also intercept.
- whenever(view.dispatchTouchEvent(any())).thenReturn(true)
- whenever(view.onInterceptTouchEvent(any())).thenReturn(true)
-
- underTest.handleExternalTouch(DOWN_EVENT)
- underTest.handleExternalTouch(MOVE_EVENT)
-
- // Once intercepted, both events are sent to the view.
- verify(view).onTouchEvent(DOWN_EVENT)
- verify(view).onTouchEvent(MOVE_EVENT)
- }
-
- @Test
- fun handleExternalTouch_notDispatched_interceptNotCalled() {
- // Don't accept dispatch
- whenever(view.dispatchTouchEvent(any())).thenReturn(false)
-
- underTest.handleExternalTouch(DOWN_EVENT)
-
- // Interception is not offered.
- verify(view, never()).onInterceptTouchEvent(any())
- }
-
- @Test
- fun handleExternalTouch_notIntercepted_onTouchNotSent() {
- // Accept dispatch, but don't dispatch
- whenever(view.dispatchTouchEvent(any())).thenReturn(true)
- whenever(view.onInterceptTouchEvent(any())).thenReturn(false)
-
- underTest.handleExternalTouch(DOWN_EVENT)
- underTest.handleExternalTouch(MOVE_EVENT)
-
- // Interception offered for both events, but onTouchEvent is never called.
- verify(view).onInterceptTouchEvent(DOWN_EVENT)
- verify(view).onInterceptTouchEvent(MOVE_EVENT)
- verify(view, never()).onTouchEvent(any())
- }
-
- @Test
fun testGetKeyguardMessageArea() =
testScope.runTest {
underTest.keyguardMessageArea
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index f380b6c..e83a46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.res.R
@@ -151,7 +152,7 @@
whenever(statusBarStateController.isDozing).thenReturn(false)
mDependency.injectTestDependency(ShadeController::class.java, shadeController)
whenever(dockManager.isDocked).thenReturn(false)
- whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING)))
.thenReturn(emptyFlow())
val featureFlags = FakeFeatureFlags()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 81d0e06..2c2fcbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.shade
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -26,8 +28,8 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
@@ -164,10 +166,10 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
val headerResourceHeight = 20
val headerHelperHeight = 30
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
.thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -187,10 +189,10 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
val headerResourceHeight = 20
val headerHelperHeight = 30
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
.thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -400,8 +402,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSplitShadeLayout_isAlignedToGuideline() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -410,8 +412,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSinglePaneLayout_childrenHaveEqualMargins() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -427,8 +429,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -445,9 +447,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -468,9 +469,9 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -491,8 +492,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
setSmallScreen()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -512,8 +513,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSinglePaneShadeLayout_isAlignedToParent() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 4ae751b..f21def3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.shade
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -27,6 +29,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
@@ -67,6 +70,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
class NotificationsQSContainerControllerTest : SysuiTestCase() {
private val view = mock<NotificationsQuickSettingsContainer>()
@@ -99,7 +103,6 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -161,8 +164,8 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val helperHeight = 30
val resourceHeight = 20
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -182,8 +185,8 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val helperHeight = 30
val resourceHeight = 20
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -424,8 +427,8 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderHelperHeight = 200
val largeScreenHeaderResourceHeight = 100
@@ -444,8 +447,8 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderHelperHeight = 200
val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 04fa590..845744a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -42,13 +42,10 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -59,9 +56,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -176,12 +171,7 @@
protected Handler mMainHandler;
protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
- protected final ShadeExpansionStateManager mShadeExpansionStateManager =
- new ShadeExpansionStateManager();
-
protected FragmentHostManager.FragmentListener mFragmentListener;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
@Before
public void setup() {
@@ -190,19 +180,11 @@
mStatusBarStateController = mKosmos.getStatusBarStateController();
mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
+ SceneInteractor sceneInteractor = mKosmos.getSceneInteractor();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
@@ -220,10 +202,6 @@
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
ResourcesSplitShadeStateController splitShadeStateController =
new ResourcesSplitShadeStateController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index 2c453a7..dfd7a71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -36,6 +37,8 @@
runCurrent()
assertThat(mQsController.isExpansionEnabled).isFalse()
+
+ coroutineContext.cancelChildren()
}
@Test
@@ -45,6 +48,8 @@
runCurrent()
assertThat(mQsController.isExpansionEnabled).isTrue()
+
+ coroutineContext.cancelChildren()
}
@Test
@@ -58,6 +63,8 @@
runCurrent()
assertThat(mQsController.isExpansionEnabled).isFalse()
+
+ coroutineContext.cancelChildren()
}
@Test
@@ -71,6 +78,8 @@
runCurrent()
assertThat(mQsController.isExpansionEnabled).isFalse()
+
+ coroutineContext.cancelChildren()
}
@Test
@@ -81,5 +90,7 @@
runCurrent()
assertThat(mQsController.isExpansionEnabled).isTrue()
+
+ coroutineContext.cancelChildren()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
index f0a457e..7d2b463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -31,16 +31,17 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-class OngoingCallBackgroundContainerTest : SysuiTestCase() {
+class ChipBackgroundContainerTest : SysuiTestCase() {
- private lateinit var underTest: OngoingCallBackgroundContainer
+ private lateinit var underTest: ChipBackgroundContainer
@Before
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null)
- underTest = chipView.requireViewById(R.id.ongoing_call_chip_background)
+ val chipView =
+ LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null)
+ underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
index 7e25aa3..b8d4e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -38,17 +38,18 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-class OngoingCallChronometerTest : SysuiTestCase() {
+class ChipChronometerTest : SysuiTestCase() {
- private lateinit var textView: OngoingCallChronometer
+ private lateinit var textView: ChipChronometer
private lateinit var doesNotFitText: String
@Before
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
- textView = chipView.findViewById(R.id.ongoing_call_chip_time)!!
+ val chipView =
+ LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+ textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!!
measureTextView()
calculateDoesNotFixText()
}
@@ -159,8 +160,8 @@
private fun measureTextView() {
textView.measure(
- View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 745d20d..1eed420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.notification.stack
+import android.platform.test.annotations.DisableFlags
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -69,8 +70,8 @@
}
@Test
+ @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
fun testShadeWidth_BasedOnFractionToShade() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -85,8 +86,8 @@
}
@Test
+ @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
fun testShelfIsLong_WhenNotOnLockscreen() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index c088609..fe6a88d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -80,6 +81,7 @@
@Mock private QuickSettingsController mQuickSettingsController;
@Mock private ShadeViewController mShadeViewController;
@Mock private PanelExpansionInteractor mPanelExpansionInteractor;
+ @Mock private Lazy<ShadeInteractor> mShadeInteractorLazy;
@Mock private ShadeHeaderController mShadeHeaderController;
@Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@@ -115,6 +117,7 @@
mShadeController,
mCommandQueue,
mPanelExpansionInteractor,
+ mShadeInteractorLazy,
mShadeHeaderController,
mRemoteInputQuickSettingsDisabler,
mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b9312d3..4488799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
import static android.provider.Settings.Global.HEADS_UP_ON;
+import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR;
import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
@@ -70,6 +71,8 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.dreams.IDreamManager;
import android.support.test.metricshelper.MetricsAsserts;
import android.testing.AndroidTestingRunner;
@@ -105,8 +108,6 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.communal.data.repository.CommunalRepository;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
@@ -224,6 +225,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
+@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
public class CentralSurfacesImplTest extends SysuiTestCase {
private static final int FOLD_STATE_FOLDED = 0;
@@ -238,8 +240,6 @@
private final TestScope mTestScope = mKosmos.getTestScope();
- private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor();
- private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository();
@Mock private NotificationsController mNotificationsController;
@Mock private LightBarController mLightBarController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -362,13 +362,9 @@
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- if (!SceneContainerFlag.isEnabled()) {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
- }
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -528,7 +524,7 @@
mScreenLifecycle,
mWakefulnessLifecycle,
mPowerInteractor,
- mCommunalInteractor,
+ mKosmos.getCommunalInteractor(),
mStatusBarStateController,
Optional.of(mBubbles),
() -> mNoteTaskController,
@@ -837,6 +833,7 @@
}
@Test
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
@@ -848,7 +845,8 @@
}
@Test
- public void testOccludingQSNotExpanded_transitionToAuthScrimmed() {
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// GIVEN device occluded and panel is NOT expanded
@@ -862,6 +860,39 @@
}
@Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and panel is NOT expanded
+ mCentralSurfaces.setBarStateForTest(SHADE);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
+
+ mCentralSurfaces.updateScrimController();
+
+ // Tests the safeguard to reset the scrimstate
+ verify(mScrimController, never()).transitionTo(any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and panel is NOT expanded
+ mCentralSurfaces.setBarStateForTest(SHADE);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
+
+ mCentralSurfaces.updateScrimController();
+
+ // Tests the safeguard to reset the scrimstate
+ verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD));
+ }
+
+ @Test
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void testOccludingQSExpanded_transitionToAuthScrimmedShade() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
@@ -878,16 +909,18 @@
@Test
public void testEnteringGlanceableHub_updatesScrim() {
// Transition to the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Communal)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
// Transition away from the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Blank)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
@@ -901,16 +934,18 @@
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
// Transition to the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Communal)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Transition away from the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Blank)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
@@ -1105,18 +1140,16 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() {
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
mCentralSurfaces.updateResources();
verify(mStatusBarWindowController, never()).refreshStatusBarHeight();
}
@Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
public void updateResources_flagDisabled_updatesStatusBarWindowHeight() {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
mCentralSurfaces.updateResources();
verify(mStatusBarWindowController).refreshStatusBarHeight();
@@ -1151,10 +1184,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1163,10 +1196,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1175,10 +1208,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1188,10 +1221,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1200,10 +1233,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1213,10 +1246,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1226,10 +1259,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 321;
@@ -1239,10 +1272,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 654;
@@ -1253,10 +1286,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 987;
@@ -1267,10 +1300,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 789;
@@ -1280,10 +1313,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 456;
@@ -1294,10 +1327,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 123;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index fd295b5..e834693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,6 +26,8 @@
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -297,8 +299,8 @@
}
@Test
+ @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
- mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
int keyguardSplitShadeTopMargin = 100;
int largeScreenHeaderHeightResource = 70;
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
@@ -316,8 +318,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
- mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
int keyguardSplitShadeTopMargin = 100;
int largeScreenHeaderHeightHelper = 50;
int largeScreenHeaderHeightResource = 70;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index dfee737..5b2526e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -71,7 +71,6 @@
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
@@ -144,8 +143,6 @@
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
-
- @Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
private TestShadeViewStateProvider mShadeViewStateProvider;
@@ -225,7 +222,6 @@
mFakeExecutor,
mBackgroundExecutor,
mLogger,
- mNotificationMediaManager,
mStatusOverlayHoverListenerFactory
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 66211c9..fdf77ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -426,7 +426,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -438,7 +438,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@@ -452,7 +452,7 @@
StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -465,7 +465,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -477,21 +477,21 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
// Ongoing call ended
when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
// Ongoing call started
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
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 05464f3..4d6798b 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
@@ -106,7 +106,7 @@
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+ chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
}
MockitoAnnotations.initMocks(this)
@@ -206,7 +206,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isEqualTo(0)
}
@@ -222,7 +222,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isGreaterThan(0)
}
@@ -237,7 +237,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isGreaterThan(0)
}
@@ -472,7 +472,10 @@
lateinit var newChipView: View
TestableLooper.get(this).runWithLooper {
- newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+ newChipView = LayoutInflater.from(mContext).inflate(
+ R.layout.ongoing_activity_chip,
+ null
+ )
}
// Change the chip view associated with the controller.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index ed7c956..a5e7a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -995,9 +995,11 @@
.setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
// All dynamic colors were added twice: light and dark them
// All fixed colors were added once
+ // All custom dynamic tokens added twice
verify(dynamic, times(
DynamicColors.allDynamicColorsMapped(false).size() * 2
- + DynamicColors.getFixedColorsMapped(false).size())
+ + DynamicColors.getFixedColorsMapped(false).size()
+ + DynamicColors.getCustomColorsMapped(false).size() * 2)
).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 948670f..01795e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.user.domain.interactor
import android.app.admin.DevicePolicyManager
+import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
@@ -59,6 +60,7 @@
@Mock private lateinit var switchUser: (Int) -> Unit
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ @Mock private lateinit var otherContext: Context
private lateinit var underTest: GuestUserInteractor
@@ -74,28 +76,30 @@
repository = FakeUserRepository()
repository.setUserInfos(ALL_USERS)
- underTest =
- GuestUserInteractor(
- applicationContext = context,
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
- manager = manager,
- repository = repository,
- deviceProvisionedController = deviceProvisionedController,
- devicePolicyManager = devicePolicyManager,
- refreshUsersScheduler =
- RefreshUsersScheduler(
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
- repository = repository,
- ),
- uiEventLogger = uiEventLogger,
- resumeSessionReceiver = resumeSessionReceiver,
- resetOrExitSessionReceiver = resetOrExitSessionReceiver,
- )
+ underTest = initGuestUserInteractor(context)
}
+ private fun initGuestUserInteractor(context: Context) =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ backgroundDispatcher = IMMEDIATE,
+ manager = manager,
+ repository = repository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ repository = repository,
+ ),
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
@Test
fun registersBroadcastReceivers() {
verify(resumeSessionReceiver).register()
@@ -103,6 +107,16 @@
}
@Test
+ fun registersBroadcastReceiversOnlyForSystemUser() {
+ for (i in 1..5) {
+ whenever(otherContext.userId).thenReturn(UserHandle.MIN_SECONDARY_USER_ID + i)
+ initGuestUserInteractor(otherContext)
+ }
+ verify(resumeSessionReceiver).register()
+ verify(resetOrExitSessionReceiver).register()
+ }
+
+ @Test
fun onDeviceBootCompleted_allowedToAdd_createGuest() =
runBlocking(IMMEDIATE) {
setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 3dee093..96c6eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -887,6 +887,46 @@
}
@Test
+ fun removeGuestUser_shouldNotShowExitGuestDialog() {
+ createUserInteractor()
+ testScope.runTest {
+ val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+ userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+ userRepository.setSelectedUserInfo(guestUserInfo)
+
+ whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+ underTest.removeGuestUser(guestUserInfo.id, userInfo.id)
+ runCurrent()
+
+ verify(manager).markGuestForDeletion(guestUserInfo.id)
+ verify(activityManager).switchUser(userInfo.id)
+ assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull()
+ }
+ }
+
+ @Test
+ fun resetGuestUser_shouldNotShowExitGuestDialog() {
+ createUserInteractor()
+ testScope.runTest {
+ val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+ val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0]
+ userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+ userRepository.setSelectedUserInfo(guestUserInfo)
+
+ whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+ whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo)
+ underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL)
+ runCurrent()
+
+ verify(manager).markGuestForDeletion(guestUserInfo.id)
+ verify(manager).createGuest(any())
+ verify(activityManager).switchUser(otherGuestUserInfo.id)
+ assertThat(collectLastValue(underTest.dialogShowRequests)())
+ .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+ }
+ }
+
+ @Test
fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
createUserInteractor()
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
index 31848a6..e1dcb14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
@@ -1,8 +1,8 @@
package com.android.systemui.util
import android.graphics.Rect
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.wm.shell.common.FloatingContentCoordinator
@@ -14,7 +14,7 @@
import org.junit.runner.RunWith
@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class FloatingContentCoordinatorTest : SysuiTestCase() {
@@ -198,12 +198,11 @@
}
/**
- * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a
- * Rect when needed.
+ * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a Rect when
+ * needed.
*/
- inner class FloatingRect(
- private val underlyingRect: Rect
- ) : FloatingContentCoordinator.FloatingContent {
+ inner class FloatingRect(private val underlyingRect: Rect) :
+ FloatingContentCoordinator.FloatingContent {
override fun moveToBounds(bounds: Rect) {
underlyingRect.set(bounds)
}
@@ -216,4 +215,4 @@
return underlyingRect
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
index 436f5b8..457f2bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
@@ -19,12 +19,13 @@
import android.content.BroadcastReceiver
import android.content.IntentFilter
import android.os.UserHandle
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.lifecycle.Observer
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -38,10 +39,9 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class RingerModeLiveDataTest : SysuiTestCase() {
@@ -52,16 +52,11 @@
private val INTENT = "INTENT"
}
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var valueSupplier: () -> Int
- @Mock
- private lateinit var observer: Observer<Int>
- @Captor
- private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
- @Captor
- private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var valueSupplier: () -> Int
+ @Mock private lateinit var observer: Observer<Int>
+ @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
// Run everything immediately
private val executor = Executor { it.run() }
@@ -88,14 +83,14 @@
fun testOnActive_broadcastRegistered() {
liveData.observeForever(observer)
verify(broadcastDispatcher)
- .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
+ .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
}
@Test
fun testOnActive_intentFilterHasIntent() {
liveData.observeForever(observer)
- verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(),
- any(), anyInt(), any())
+ verify(broadcastDispatcher)
+ .registerReceiver(any(), capture(intentFilterCaptor), any(), any(), anyInt(), any())
assertTrue(intentFilterCaptor.value.hasAction(INTENT))
}
@@ -111,4 +106,4 @@
liveData.removeObserver(observer)
verify(broadcastDispatcher).unregisterReceiver(any())
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index b13cb72..6271904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -19,10 +19,10 @@
import android.app.WallpaperInfo
import android.app.WallpaperManager
import android.os.IBinder
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.view.ViewRootImpl
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
@@ -32,36 +32,30 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doThrow
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@RunWithLooper
@SmallTest
class WallpaperControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var wallpaperManager: WallpaperManager
- @Mock
- private lateinit var root: View
- @Mock
- private lateinit var viewRootImpl: ViewRootImpl
- @Mock
- private lateinit var windowToken: IBinder
+ @Mock private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var root: View
+ @Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var windowToken: IBinder
private val wallpaperRepository = FakeWallpaperRepository()
- @JvmField
- @Rule
- val mockitoRule = MockitoJUnit.rule()
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
private lateinit var wallaperController: WallpaperController
@@ -92,9 +86,7 @@
@Test
fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
- wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
- useDefaultTransition = false
- )
+ wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(useDefaultTransition = false)
wallaperController.setUnfoldTransitionZoom(0.5f)
@@ -130,7 +122,8 @@
@Test
fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() {
- doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
+ doThrow(IllegalArgumentException("test exception"))
+ .`when`(wallpaperManager)
.setWallpaperZoomOut(any(), anyFloat())
wallaperController.setNotificationShadeZoom(0.5f)
@@ -140,8 +133,7 @@
private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo {
val info = mock(WallpaperInfo::class.java)
- whenever(info.shouldUseDefaultUnfoldTransition())
- .thenReturn(useDefaultTransition)
+ whenever(info.shouldUseDefaultUnfoldTransition()).thenReturn(useDefaultTransition)
return info
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
index 92afb03..b26598c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
@@ -16,13 +16,16 @@
package com.android.systemui.util.animation
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import org.junit.Test
import java.lang.IllegalArgumentException
+import org.junit.runner.RunWith
+import org.junit.Test
@SmallTest
+@RunWith(AndroidJUnit4::class)
class AnimationUtilTest : SysuiTestCase() {
@Test
fun getMsForFrames_5frames_returns83() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index 9dfa14d..7dfac0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -22,8 +22,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -39,7 +38,7 @@
import java.util.List;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class FakeExecutorTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
index 78fc680..48fb745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
@@ -24,8 +24,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -39,7 +38,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class MessageRouterImplTest extends SysuiTestCase {
private static final int MESSAGE_A = 0;
private static final int MESSAGE_B = 1;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
index 15032dc..7ec420f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.util.concurrency
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.time.FakeSystemClock
@@ -26,7 +26,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MockExecutorHandlerTest : SysuiTestCase() {
/** Test FakeExecutor that receives non-delayed items to execute. */
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
index 00f37ae..13fff29d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
@@ -18,8 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -30,7 +29,7 @@
import org.junit.runner.RunWith;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class RepeatableExecutorTest extends SysuiTestCase {
private static final int DELAY = 100;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
index b367a60..37015e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -23,8 +23,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.CoreStartable;
@@ -44,7 +43,7 @@
import java.util.Set;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ConditionalCoreStartableTest extends SysuiTestCase {
public static class FakeConditionalCoreStartable extends ConditionalCoreStartable {
interface Callback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
index ac357ea..b8f5815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
@@ -4,7 +4,7 @@
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ShapeDrawable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
@@ -12,7 +12,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class DrawableSizeTest : SysuiTestCase() {
@@ -32,14 +32,11 @@
@Test
fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() {
- val drawable = BitmapDrawable(resources,
- Bitmap.createBitmap(
- resources.displayMetrics,
- 150,
- 150,
- Bitmap.Config.ARGB_8888
- )
- )
+ val drawable =
+ BitmapDrawable(
+ resources,
+ Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888)
+ )
val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300)
assertThat(result).isSameInstanceAs(drawable)
}
@@ -48,14 +45,11 @@
fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() {
// This bitmap would actually fail to resize if the method doesn't check for
// bitmap dimensions inside drawable.
- val drawable = BitmapDrawable(resources,
- Bitmap.createBitmap(
- resources.displayMetrics,
- 150,
- 75,
- Bitmap.Config.ARGB_8888
- )
- )
+ val drawable =
+ BitmapDrawable(
+ resources,
+ Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888)
+ )
val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75)
assertThat(result).isNotSameInstanceAs(drawable)
@@ -65,9 +59,9 @@
@Test
fun testDownscaleToSize_drawableAnimated_unchanged() {
- val drawable = resources.getDrawable(android.R.drawable.stat_sys_download,
- resources.newTheme())
+ val drawable =
+ resources.getDrawable(android.R.drawable.stat_sys_download, resources.newTheme())
val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1)
assertThat(result).isSameInstanceAs(drawable)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 7d0d57b..e2ce50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.time.FakeSystemClock
@@ -47,7 +47,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class PairwiseFlowTest : SysuiTestCase() {
@Test
fun simple() = runBlocking {
@@ -89,7 +89,9 @@
initRun = true
"initial"
}
- ) { prev: String, next: String -> "$prev|$next" }
+ ) { prev: String, next: String ->
+ "$prev|$next"
+ }
)
.emitsExactly("initial|val1", "val1|val2")
assertThat(initRun).isTrue()
@@ -104,7 +106,9 @@
initRun = true
"initial"
}
- ) { prev: String, next: String -> "$prev|$next" }
+ ) { prev: String, next: String ->
+ "$prev|$next"
+ }
)
.emitsNothing()
// Even though the flow will not emit anything, the initial value function should still get
@@ -120,7 +124,9 @@
initRun = true
"initial"
}
- ) { prev: String, next: String -> "$prev|$next" }
+ ) { prev: String, next: String ->
+ "$prev|$next"
+ }
// Since the flow isn't collected, ensure [initialValueFun] isn't run.
assertThat(initRun).isFalse()
@@ -146,7 +152,7 @@
}
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class SetChangesFlowTest : SysuiTestCase() {
@Test
fun simple() = runBlocking {
@@ -198,7 +204,7 @@
}
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class SampleFlowTest : SysuiTestCase() {
@Test
fun simple() = runBlocking {
@@ -240,7 +246,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ThrottleFlowTest : SysuiTestCase() {
@Test
@@ -248,13 +254,16 @@
// Arrange
val choreographer = createChoreographer(this)
val output = mutableListOf<Int>()
- val collectJob = backgroundScope.launch {
- flow {
- emit(1)
- delay(1000)
- emit(2)
- }.throttle(1000, choreographer.fakeClock).toList(output)
- }
+ val collectJob =
+ backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(1000)
+ emit(2)
+ }
+ .throttle(1000, choreographer.fakeClock)
+ .toList(output)
+ }
// Act
choreographer.advanceAndRun(0)
@@ -283,13 +292,16 @@
// Arrange
val choreographer = createChoreographer(this)
val output = mutableListOf<Int>()
- val collectJob = backgroundScope.launch {
- flow {
- emit(1)
- delay(500)
- emit(2)
- }.throttle(1000, choreographer.fakeClock).toList(output)
- }
+ val collectJob =
+ backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ }
+ .throttle(1000, choreographer.fakeClock)
+ .toList(output)
+ }
// Act
choreographer.advanceAndRun(0)
@@ -319,15 +331,18 @@
// Arrange
val choreographer = createChoreographer(this)
val output = mutableListOf<Int>()
- val collectJob = backgroundScope.launch {
- flow {
- emit(1)
- delay(500)
- emit(2)
- delay(500)
- emit(3)
- }.throttle(1000, choreographer.fakeClock).toList(output)
- }
+ val collectJob =
+ backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ delay(500)
+ emit(3)
+ }
+ .throttle(1000, choreographer.fakeClock)
+ .toList(output)
+ }
// Act
choreographer.advanceAndRun(0)
@@ -357,15 +372,18 @@
// Arrange
val choreographer = createChoreographer(this)
val output = mutableListOf<Int>()
- val collectJob = backgroundScope.launch {
- flow {
- emit(1)
- delay(500)
- emit(2)
- delay(250)
- emit(3)
- }.throttle(1000, choreographer.fakeClock).toList(output)
- }
+ val collectJob =
+ backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ delay(250)
+ emit(3)
+ }
+ .throttle(1000, choreographer.fakeClock)
+ .toList(output)
+ }
// Act
choreographer.advanceAndRun(0)
@@ -391,15 +409,16 @@
collectJob.cancel()
}
- private fun createChoreographer(testScope: TestScope) = object {
- val fakeClock = FakeSystemClock()
+ private fun createChoreographer(testScope: TestScope) =
+ object {
+ val fakeClock = FakeSystemClock()
- fun advanceAndRun(millis: Long) {
- fakeClock.advanceTime(millis)
- testScope.advanceTimeBy(millis)
- testScope.runCurrent()
+ fun advanceAndRun(millis: Long) {
+ fakeClock.advanceTime(millis)
+ testScope.advanceTimeBy(millis)
+ testScope.runCurrent()
+ }
}
- }
}
private fun <T> assertThatFlow(flow: Flow<T>) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
index 4ca1fd3..c31b287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import java.util.concurrent.atomic.AtomicLong
@@ -31,43 +31,42 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class IpcSerializerTest : SysuiTestCase() {
private val serializer = IpcSerializer()
@Ignore("b/253046405")
@Test
- fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
- val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
- withContext(Dispatchers.IO) {
- val lastEvaluatedTime = AtomicLong(System.currentTimeMillis())
- // First, launch many serialization requests in parallel
- repeat(100_000) {
- launch(Dispatchers.Unconfined) {
- val enqueuedTime = System.currentTimeMillis()
- serializer.runSerialized {
- val last = lastEvaluatedTime.getAndSet(enqueuedTime)
- assertTrue(
- "expected $last less than or equal to $enqueuedTime ",
- last <= enqueuedTime,
- )
+ fun serializeManyIncomingIpcs(): Unit =
+ runBlocking(Dispatchers.Main.immediate) {
+ val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
+ withContext(Dispatchers.IO) {
+ val lastEvaluatedTime = AtomicLong(System.currentTimeMillis())
+ // First, launch many serialization requests in parallel
+ repeat(100_000) {
+ launch(Dispatchers.Unconfined) {
+ val enqueuedTime = System.currentTimeMillis()
+ serializer.runSerialized {
+ val last = lastEvaluatedTime.getAndSet(enqueuedTime)
+ assertTrue(
+ "expected $last less than or equal to $enqueuedTime ",
+ last <= enqueuedTime,
+ )
+ }
}
}
+ // Then, process them all in the order they came in.
+ processor.start()
}
- // Then, process them all in the order they came in.
- processor.start()
+ // All done, stop processing
+ processor.cancel()
}
- // All done, stop processing
- processor.cancel()
- }
@Test(timeout = 5000)
fun serializeOnOneThread_doesNotDeadlock() = runBlocking {
val job = launch { serializer.process() }
- repeat(100) {
- serializer.runSerializedBlocking { }
- }
+ repeat(100) { serializer.runSerializedBlocking {} }
job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
index 2013bb0..8bfff9c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -27,13 +27,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameters
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
index 6848b83..b2f7c1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
@@ -28,7 +28,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class RaceSuspendTest : SysuiTestCase() {
@Test
fun raceSimple() = runBlocking {
@@ -46,10 +46,11 @@
@Test
fun raceImmediate() = runBlocking {
assertThat(
- race<Int>(
- { 1 },
- { 2 },
+ race<Int>(
+ { 1 },
+ { 2 },
+ )
)
- ).isEqualTo(1)
+ .isEqualTo(1)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
index 84129be..300c298 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
@@ -26,9 +26,9 @@
import android.content.res.Resources;
import android.hardware.Sensor;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -46,7 +46,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class PostureDependentProximitySensorTest extends SysuiTestCase {
@Mock private Resources mResources;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 19dbf9a..5dd008a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -23,9 +23,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
import java.util.function.Consumer;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class ProximityCheckTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
index 5e75578..0eab74e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
@@ -24,9 +24,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class ProximitySensorImplDualTest extends SysuiTestCase {
private ProximitySensor mProximitySensor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
index 752cd32..f44c842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
@@ -21,9 +21,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
* Tests for ProximitySensor that rely on a single hardware sensor.
*/
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class ProximitySensorImplSingleTest extends SysuiTestCase {
private ProximitySensor mProximitySensor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 8d26c87..a54afad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -30,8 +30,8 @@
import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -49,7 +49,7 @@
import java.util.Objects;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ObservableServiceConnectionTest extends SysuiTestCase {
static class Foo {
int mValue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
index a2fd288..a70b00c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -24,8 +24,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -38,7 +38,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class PackageObserverTest extends SysuiTestCase {
@Mock
Context mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
index 55c49ee..ef10fdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -19,8 +19,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -37,7 +36,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class PersistentConnectionManagerTest extends SysuiTestCase {
private static final int MAX_RETRIES = 5;
private static final int RETRY_DELAY_MS = 1000;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index f65caee2..99f6303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -28,8 +28,8 @@
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -44,7 +44,7 @@
import java.util.Map;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class FakeSettingsTest extends SysuiTestCase {
@Mock
ContentObserver mContentObserver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index 913759f..88b2630 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.util.settings.repository
import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -33,11 +34,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
private val dispatcher = StandardTestDispatcher()
@@ -48,11 +48,12 @@
@Before
fun setup() {
- repository = UserAwareSecureSettingsRepositoryImpl(
- secureSettings,
- userRepository,
- dispatcher,
- )
+ repository =
+ UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher,
+ )
userRepository.setUserInfos(USER_INFOS)
setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
@@ -105,4 +106,4 @@
val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index 94100fe..6637d5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.util.ui
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -30,7 +30,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class AnimatedValueTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
index e3cd9b2..3dcb828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -19,17 +19,20 @@
import android.graphics.Rect
import android.view.View
import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
@SmallTest
+@RunWith(AndroidJUnit4::class)
class ViewUtilTest : SysuiTestCase() {
private val viewUtil = ViewUtil()
private lateinit var view: View
@@ -45,11 +48,13 @@
location[1] = VIEW_TOP
`when`(view.locationOnScreen).thenReturn(location)
doAnswer { invocation ->
- val pos = invocation.arguments[0] as IntArray
- pos[0] = VIEW_LEFT
- pos[1] = VIEW_TOP
- null
- }.`when`(view).getLocationInWindow(any())
+ val pos = invocation.arguments[0] as IntArray
+ pos[0] = VIEW_LEFT
+ pos[1] = VIEW_TOP
+ null
+ }
+ .`when`(view)
+ .getLocationInWindow(any())
}
@Test
@@ -59,9 +64,8 @@
@Test
fun touchIsWithinView_onTopLeftCorner_returnsTrue() {
- assertThat(viewUtil.touchIsWithinView(
- view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())
- ).isTrue()
+ assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat()))
+ .isTrue()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index ed07ad2..207c35d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -35,15 +35,18 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.util.List;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+
@SmallTest
-@RunWith(Parameterized.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class WakeLockTest extends SysuiTestCase {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
public static List<FlagsParameterization> getFlags() {
return FlagsParameterization.allCombinationsOf(
Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD);
@@ -114,4 +117,4 @@
// shouldn't throw an exception on production builds
mWakeLock.release(WHY);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aac3640..df78110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR;
import static com.google.common.truth.Truth.assertThat;
@@ -2141,6 +2142,112 @@
assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+ // Drag first bubble, bubble should collapse
+ mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+ // Stop dragging, first bubble should be expanded
+ mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+ // Drag second bubble, bubble should collapse
+ mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+ // Stop dragging, first bubble should be expanded
+ mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ // Drag first bubble to dismiss
+ mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+ mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey());
+ // Second bubble is selected and expanded
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ // Drag second bubble to dismiss
+ mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+ mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey());
+ // First bubble remains selected and expanded
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
@DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
@Test
public void doesNotRegisterSensitiveStateListener() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 9dcd946..8eef930 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -99,8 +97,22 @@
.setProvideMainThread(true)
.build();
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule =
+ new SetFlagsRule.ClassRule(
+ android.app.Flags.class,
+ android.hardware.biometrics.Flags.class,
+ android.multiuser.Flags.class,
+ android.net.platform.flags.Flags.class,
+ android.os.Flags.class,
+ android.service.controls.flags.Flags.class,
+ com.android.internal.telephony.flags.Flags.class,
+ com.android.server.notification.Flags.class,
+ com.android.systemui.Flags.class);
+
+ // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly
+ @Rule public final SetFlagsRule mSetFlagsRule =
+ isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
@Rule(order = 10)
public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 2ae6f542..4999a5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.launcher3.icons.IconProvider
import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
@@ -32,6 +34,8 @@
context = applicationContext,
udfpsOverlayInteractor = udfpsOverlayInteractor,
biometricStatusInteractor = biometricStatusInteractor,
- udfpsUtils = udfpsUtils
+ udfpsUtils = udfpsUtils,
+ iconProvider = IconProvider(applicationContext),
+ activityTaskManager = activityTaskManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
index d3ceb15..f5d02f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -38,4 +38,8 @@
)
)
}
+
+ fun setBaseUserRestriction() {
+ _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 38f2a56..3401cc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -27,6 +27,9 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.displayTracker
val Kosmos.shortcutHelperRepository by
Kosmos.Fixture { ShortcutHelperRepository(fakeCommandQueue, broadcastDispatcher) }
@@ -42,7 +45,9 @@
}
val Kosmos.shortcutHelperInteractor by
- Kosmos.Fixture { ShortcutHelperInteractor(shortcutHelperRepository) }
+ Kosmos.Fixture {
+ ShortcutHelperInteractor(displayTracker, testScope, sysUiState, shortcutHelperRepository)
+ }
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 408157b..3e69e87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -17,7 +17,10 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
Kosmos.Fixture { fakeKeyguardTransitionRepository }
var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
+ Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
new file mode 100644
index 0000000..7d0e8b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lockscreenSceneTransitionRepository by
+ Kosmos.Fixture { LockscreenSceneTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2c6d44f..03e5a90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -29,5 +30,6 @@
transitionInteractor = keyguardTransitionInteractor,
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8e..c90642d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
@@ -32,5 +33,6 @@
fromAodTransitionInteractor = { fromAodTransitionInteractor },
fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
+ sceneInteractor = { sceneInteractor }
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
new file mode 100644
index 0000000..3c1f7b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.scenetransition
+
+import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+var Kosmos.lockscreenSceneTransitionInteractor by
+ Kosmos.Fixture {
+ LockscreenSceneTransitionInteractor(
+ transitionInteractor = keyguardTransitionInteractor,
+ applicationScope = applicationCoroutineScope,
+ sceneInteractor = sceneInteractor,
+ repository = lockscreenSceneTransitionRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index 460913f..b8fcec6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
AodToLockscreenTransitionViewModel(
- deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
shadeInteractor = shadeInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
new file mode 100644
index 0000000..8ad6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.qrcodescanner
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff..f50443e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@
private val mutableInputs = mutableListOf<Input>()
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
mutableInputs.add(Input.Intent(expandable, intent))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
new file mode 100644
index 0000000..ccfb609
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.actions
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
new file mode 100644
index 0000000..146c1ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
new file mode 100644
index 0000000..9ad49f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 0000000..dcfcce7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+ Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+ Kosmos.Fixture {
+ QRCodeScannerTileDataInteractor(
+ backgroundCoroutineContext,
+ applicationCoroutineScope,
+ qrCodeScannerController
+ )
+ }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+ Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+ Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+ Kosmos.Fixture {
+ QSTileViewModelImpl(
+ qsQRCodeScannerTileConfig,
+ { qrCodeScannerTileUserActionInteractor },
+ { qrCodeScannerTileDataInteractor },
+ { qrCodeScannerTileMapper },
+ fakeDisabledByPolicyInteractor,
+ fakeUserRepository,
+ fakeFalsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ systemClock,
+ testDispatcher,
+ testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
new file mode 100644
index 0000000..641a757
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.data.repository
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+private val mutableTransitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
+
+fun Kosmos.setSceneTransition(
+ transition: ObservableTransitionState,
+ scope: TestScope = testScope,
+ repository: SceneContainerRepository = sceneContainerRepository
+) {
+ repository.setTransitionState(mutableTransitionState)
+ mutableTransitionState.value = transition
+ scope.runCurrent()
+}
+
+fun Transition(
+ from: SceneKey,
+ to: SceneKey,
+ currentScene: Flow<SceneKey> = flowOf(to),
+ progress: Flow<Float> = flowOf(0f),
+ isInitiatedByUserInput: Boolean = false,
+ isUserInputOngoing: Flow<Boolean> = flowOf(false),
+): ObservableTransitionState.Transition {
+ return ObservableTransitionState.Transition(
+ fromScene = from,
+ toScene = to,
+ currentScene = currentScene,
+ progress = progress,
+ isInitiatedByUserInput = isInitiatedByUserInput,
+ isUserInputOngoing = isUserInputOngoing
+ )
+}
+
+fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle {
+ return ObservableTransitionState.Idle(currentScene)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 2bd584e..562ac0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -27,6 +27,8 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
+import androidx.annotation.NonNull;
+
import com.android.internal.logging.InstanceId;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
@@ -36,6 +38,7 @@
import kotlin.Unit;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Combined builder for constructing a NotificationEntry and its associated StatusBarNotification
@@ -73,6 +76,20 @@
mCreationTime = source.getCreationTime();
}
+ /** Allows the caller to sub-build the ranking */
+ @NonNull
+ public NotificationEntryBuilder updateRanking(@NonNull Consumer<RankingBuilder> rankingUpdater) {
+ rankingUpdater.accept(mRankingBuilder);
+ return this;
+ }
+
+ /** Allows the caller to sub-build the SBN */
+ @NonNull
+ public NotificationEntryBuilder updateSbn(@NonNull Consumer<SbnBuilder> sbnUpdater) {
+ sbnUpdater.accept(mSbnBuilder);
+ return this;
+ }
+
/** Update an the parent on an existing entry */
public static void setNewParent(NotificationEntry entry, GroupEntry parent) {
entry.setParent(parent);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 59bbff1..1b58582 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -18,8 +18,6 @@
import android.content.packageManager
import android.content.pm.ApplicationInfo
-import android.os.Handler
-import android.os.looper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogManager
@@ -32,6 +30,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor
val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() }
val Kosmos.localMediaRepositoryFactory by
@@ -53,7 +52,7 @@
testScope.backgroundScope,
testScope.testScheduler,
mediaControllerRepository,
- Handler(looper),
+ mediaControllerInteractor,
)
}
@@ -61,7 +60,7 @@
Kosmos.Fixture {
MediaDeviceSessionInteractor(
testScope.testScheduler,
- Handler(looper),
+ mediaControllerInteractor,
mediaControllerRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 617fc52..6b27079 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.data.repository
import android.media.AudioDeviceInfo
+import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
@@ -29,10 +30,10 @@
class FakeAudioRepository : AudioRepository {
- private val mutableMode = MutableStateFlow(0)
+ private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
override val mode: StateFlow<Int> = mutableMode.asStateFlow()
- private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL))
override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow()
private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
@@ -53,7 +54,7 @@
audioStream = audioStream,
volume = 0,
minVolume = 0,
- maxVolume = 0,
+ maxVolume = 10,
isAffectedByRingerMode = false,
isMuted = false,
)
@@ -67,8 +68,14 @@
getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
}
- override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
- getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+ val modelState = getAudioStreamModelState(audioStream)
+ return if (modelState.value.isMuted == isMuted) {
+ false
+ } else {
+ modelState.update { it.copy(isMuted = isMuted) }
+ true
+ }
}
override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt
new file mode 100644
index 0000000..f03ec01
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import android.media.session.MediaController
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+class FakeMediaControllerInteractor : MediaControllerInteractor {
+
+ private val stateChanges = MutableSharedFlow<MediaControllerChangeModel>(replay = 1)
+
+ override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> =
+ stateChanges
+
+ fun updateState(change: MediaControllerChangeModel) {
+ stateChanges.tryEmit(change)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
new file mode 100644
index 0000000..652b3ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.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.volume.panel.component.mediaoutput.domain.interactor
+
+import android.os.Handler
+import android.os.looper
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
+ Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto
index fc9f7a45..c2b3ac2 100644
--- a/proto/src/am_capabilities.proto
+++ b/proto/src/am_capabilities.proto
@@ -15,8 +15,16 @@
string name = 1;
}
+message VMInfo {
+ // The value of the "java.vm.name" system property
+ string name = 1;
+ // The value of the "java.vm.version" system property
+ string version = 2;
+}
+
message Capabilities {
repeated Capability values = 1;
repeated VMCapability vm_capabilities = 2;
repeated FrameworkCapability framework_capabilities = 3;
+ VMInfo vm_info = 4;
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index bc608c5..95cbb6b 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -221,7 +221,9 @@
data: [
":framework-minus-apex.ravenwood.stats",
":framework-minus-apex.ravenwood.apis",
+ ":framework-minus-apex.ravenwood.keep_all",
":services.core.ravenwood.stats",
":services.core.ravenwood.apis",
+ ":services.core.ravenwood.keep_all",
],
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
new file mode 100644
index 0000000..4992c4b
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
@@ -0,0 +1,753 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A subclass of Context with all the abstract methods replaced with concrete methods.
+ *
+ * <p>In order to make sure it implements all the abstract methods, we intentionally keep it
+ * non-abstract.
+ */
+public class RavenwoodBaseContext extends Context {
+ RavenwoodBaseContext() {
+ // Only usable by ravenwood.
+ }
+
+ private static RuntimeException notSupported() {
+ return new RuntimeException("This Context API is not yet supported under"
+ + " the Ravenwood deviceless testing environment. Contact g/ravenwood");
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ throw notSupported();
+ }
+
+ @Override
+ public Resources getResources() {
+ throw notSupported();
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ throw notSupported();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ throw notSupported();
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ throw notSupported();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ throw notSupported();
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ throw notSupported();
+ }
+
+ @Override
+ public Theme getTheme() {
+ throw notSupported();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ throw notSupported();
+ }
+
+ @Override
+ public String getPackageName() {
+ throw notSupported();
+ }
+
+ @Override
+ public String getBasePackageName() {
+ throw notSupported();
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ throw notSupported();
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ throw notSupported();
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ throw notSupported();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ throw notSupported();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean deleteSharedPreferences(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public void reloadSharedPreferences() {
+ throw notSupported();
+ }
+
+ @Override
+ public FileInputStream openFileInput(String name) throws FileNotFoundException {
+ throw notSupported();
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean deleteFile(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public File getFileStreamPath(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public File getSharedPreferencesPath(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public File getDataDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getFilesDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getNoBackupFilesDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getExternalFilesDir(String type) {
+ throw notSupported();
+ }
+
+ @Override
+ public File[] getExternalFilesDirs(String type) {
+ throw notSupported();
+ }
+
+ @Override
+ public File getObbDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File[] getObbDirs() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getCacheDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getCodeCacheDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getExternalCacheDir() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getPreloadsFileCache() {
+ throw notSupported();
+ }
+
+ @Override
+ public File[] getExternalCacheDirs() {
+ throw notSupported();
+ }
+
+ @Override
+ public File[] getExternalMediaDirs() {
+ throw notSupported();
+ }
+
+ @Override
+ public String[] fileList() {
+ throw notSupported();
+ }
+
+ @Override
+ public File getDir(String name, int mode) {
+ throw notSupported();
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+ throw notSupported();
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
+ DatabaseErrorHandler errorHandler) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean moveDatabaseFrom(Context sourceContext, String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean deleteDatabase(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public File getDatabasePath(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public String[] databaseList() {
+ throw notSupported();
+ }
+
+ @Override
+ public Drawable getWallpaper() {
+ throw notSupported();
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ throw notSupported();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ throw notSupported();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ throw notSupported();
+ }
+
+ @Override
+ public void setWallpaper(Bitmap bitmap) throws IOException {
+ throw notSupported();
+ }
+
+ @Override
+ public void setWallpaper(InputStream data) throws IOException {
+ throw notSupported();
+ }
+
+ @Override
+ public void clearWallpaper() throws IOException {
+ throw notSupported();
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ throw notSupported();
+ }
+
+ @Override
+ public void startActivity(Intent intent, Bundle options) {
+ throw notSupported();
+ }
+
+ @Override
+ public void startActivities(Intent[] intents) {
+ throw notSupported();
+ }
+
+ @Override
+ public void startActivities(Intent[] intents, Bundle options) {
+ throw notSupported();
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
+ int flagsValues, int extraFlags) throws SendIntentException {
+ throw notSupported();
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
+ int flagsValues, int extraFlags, Bundle options) throws SendIntentException {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+ String[] receiverPermissions) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission,
+ int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+ Bundle options) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+ int appOp) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, Bundle options,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+ throw notSupported();
+
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ throw notSupported();
+
+ }
+
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ throw notSupported();
+
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ throw notSupported();
+ }
+
+ @Override
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler, int flags) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ throw notSupported();
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
+ throw notSupported();
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ throw notSupported();
+ }
+
+ @Override
+ public ComponentName startService(Intent service) {
+ throw notSupported();
+ }
+
+ @Override
+ public ComponentName startForegroundService(Intent service) {
+ throw notSupported();
+ }
+
+ @Override
+ public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean stopService(Intent service) {
+ throw notSupported();
+ }
+
+ @Override
+ public ComponentName startServiceAsUser(Intent service, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean stopServiceAsUser(Intent service, UserHandle user) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+ throw notSupported();
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName className, String profileFile,
+ Bundle arguments) {
+ throw notSupported();
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ throw notSupported();
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkCallingPermission(String permission) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkSelfPermission(String permission) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforcePermission(String permission, int pid, int uid, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public void revokeUriPermission(Uri uri, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri uri, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, String readPermission, String writePermission,
+ int pid, int uid, int modeFlags) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public void enforceUriPermission(Uri uri, String readPermission, String writePermission,
+ int pid, int uid, int modeFlags, String message) {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws NameNotFoundException {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createApplicationContext(ApplicationInfo application, int flags)
+ throws NameNotFoundException {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createContextForSplit(String splitName) throws NameNotFoundException {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createDeviceProtectedStorageContext() {
+ throw notSupported();
+ }
+
+ @Override
+ public Context createCredentialProtectedStorageContext() {
+ throw notSupported();
+ }
+
+ @Override
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ throw notSupported();
+ }
+
+ @Override
+ public int getDisplayId() {
+ throw notSupported();
+ }
+
+ @Override
+ public void updateDisplay(int displayId) {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean isDeviceProtectedStorage() {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean isCredentialProtectedStorage() {
+ throw notSupported();
+ }
+
+ @Override
+ public boolean canLoadUnsafeResources() {
+ throw notSupported();
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 109ef76..1dd5e1d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -28,7 +28,6 @@
import android.os.UserHandle;
import android.ravenwood.example.BlueManager;
import android.ravenwood.example.RedManager;
-import android.test.mock.MockContext;
import android.util.ArrayMap;
import android.util.Singleton;
@@ -36,7 +35,7 @@
import java.util.concurrent.Executor;
import java.util.function.Supplier;
-public class RavenwoodContext extends MockContext {
+public class RavenwoodContext extends RavenwoodBaseContext {
private final String mPackageName;
private final HandlerThread mMainThread;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 96b7057..69ff262 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -162,20 +162,23 @@
android.graphics.Interpolator.class,
android.graphics.Matrix.class,
android.graphics.Path.class,
+ android.graphics.Color.class,
+ android.graphics.ColorSpace.class,
};
/**
- * @return if a given class has any native method or not.
+ * @return if a given class and its nested classes, if any, have any native method or not.
*/
private static boolean hasNativeMethod(Class<?> clazz) {
- for (var method : clazz.getDeclaredMethods()) {
- if (Modifier.isNative(method.getModifiers())) {
- return true;
+ for (var nestedClass : clazz.getNestMembers()) {
+ for (var method : nestedClass.getDeclaredMethods()) {
+ if (Modifier.isNative(method.getModifiers())) {
+ return true;
+ }
}
}
return false;
}
-
/**
* Create a list of classes as comma-separated that require JNI methods to be set up from
* a given class list, ignoring classes with no native methods.
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index cf58bd2..43b61a4 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -18,8 +18,14 @@
set -e
# Output files
-stats=/tmp/ravenwood-stats-all.csv
-apis=/tmp/ravenwood-apis-all.csv
+out_dir=/tmp/ravenwood
+stats=$out_dir/ravenwood-stats-all.csv
+apis=$out_dir/ravenwood-apis-all.csv
+keep_all_dir=$out_dir/ravenwood-keep-all/
+
+rm -fr $out_dir
+mkdir -p $out_dir
+mkdir -p $keep_all_dir
# Where the input files are.
path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
@@ -76,3 +82,7 @@
collect_stats $stats
collect_apis $apis
+
+cp *keep_all.txt $keep_all_dir
+echo "Keep all files created at:"
+find $keep_all_dir -type f
\ No newline at end of file
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index e452299..f3172ae 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -1,5 +1,7 @@
# Only classes listed here can use the Ravenwood annotations.
+com.android.internal.ravenwood.*
+
com.android.internal.display.BrightnessSynchronizer
com.android.internal.util.ArrayUtils
com.android.internal.logging.MetricsLogger
@@ -237,6 +239,8 @@
android.accounts.Account
android.graphics.Bitmap$Config
+android.graphics.Color
+android.graphics.ColorSpace
android.graphics.Insets
android.graphics.Interpolator
android.graphics.Matrix
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 371c3ac..9d29a05 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -1,59 +1,59 @@
# Ravenwood "policy" file for framework-minus-apex.
# Keep all AIDL interfaces
-class :aidl stubclass
+class :aidl keepclass
# Keep all feature flag implementations
-class :feature_flags stubclass
+class :feature_flags keepclass
# Keep all sysprops generated code implementations
-class :sysprops stubclass
+class :sysprops keepclass
# Exported to Mainline modules; cannot use annotations
-class com.android.internal.util.FastXmlSerializer stubclass
-class com.android.internal.util.FileRotator stubclass
-class com.android.internal.util.HexDump stubclass
-class com.android.internal.util.IndentingPrintWriter stubclass
-class com.android.internal.util.LocalLog stubclass
-class com.android.internal.util.MessageUtils stubclass
-class com.android.internal.util.TokenBucket stubclass
-class android.os.HandlerExecutor stubclass
-class android.util.BackupUtils stubclass
-class android.util.IndentingPrintWriter stubclass
-class android.util.LocalLog stubclass
-class android.util.Pair stubclass
-class android.util.Rational stubclass
+class com.android.internal.util.FastXmlSerializer keepclass
+class com.android.internal.util.FileRotator keepclass
+class com.android.internal.util.HexDump keepclass
+class com.android.internal.util.IndentingPrintWriter keepclass
+class com.android.internal.util.LocalLog keepclass
+class com.android.internal.util.MessageUtils keepclass
+class com.android.internal.util.TokenBucket keepclass
+class android.os.HandlerExecutor keepclass
+class android.util.BackupUtils keepclass
+class android.util.IndentingPrintWriter keepclass
+class android.util.LocalLog keepclass
+class android.util.Pair keepclass
+class android.util.Rational keepclass
# From modules-utils; cannot use annotations
-class com.android.internal.util.Preconditions stubclass
-class com.android.internal.logging.InstanceId stubclass
-class com.android.internal.logging.InstanceIdSequence stubclass
-class com.android.internal.logging.UiEvent stubclass
-class com.android.internal.logging.UiEventLogger stubclass
+class com.android.internal.util.Preconditions keepclass
+class com.android.internal.logging.InstanceId keepclass
+class com.android.internal.logging.InstanceIdSequence keepclass
+class com.android.internal.logging.UiEvent keepclass
+class com.android.internal.logging.UiEventLogger keepclass
# From modules-utils; cannot use annotations
-class com.android.modules.utils.BinaryXmlPullParser stubclass
-class com.android.modules.utils.BinaryXmlSerializer stubclass
-class com.android.modules.utils.FastDataInput stubclass
-class com.android.modules.utils.FastDataOutput stubclass
-class com.android.modules.utils.ModifiedUtf8 stubclass
-class com.android.modules.utils.TypedXmlPullParser stubclass
-class com.android.modules.utils.TypedXmlSerializer stubclass
+class com.android.modules.utils.BinaryXmlPullParser keepclass
+class com.android.modules.utils.BinaryXmlSerializer keepclass
+class com.android.modules.utils.FastDataInput keepclass
+class com.android.modules.utils.FastDataOutput keepclass
+class com.android.modules.utils.ModifiedUtf8 keepclass
+class com.android.modules.utils.TypedXmlPullParser keepclass
+class com.android.modules.utils.TypedXmlSerializer keepclass
# Uri
-class android.net.Uri stubclass
-class android.net.UriCodec stubclass
+class android.net.Uri keepclass
+class android.net.UriCodec keepclass
# Telephony
-class android.telephony.PinResult stubclass
+class android.telephony.PinResult keepclass
# Just enough to support mocking, no further functionality
-class android.content.BroadcastReceiver stub
- method <init> ()V stub
-class android.content.Context stub
- method <init> ()V stub
- method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
-class android.content.pm.PackageManager stub
- method <init> ()V stub
-class android.text.ClipboardManager stub
- method <init> ()V stub
+class android.content.BroadcastReceiver keep
+ method <init> ()V keep
+class android.content.Context keep
+ method <init> ()V keep
+ method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep
+class android.content.pm.PackageManager keep
+ method <init> ()V keep
+class android.text.ClipboardManager keep
+ method <init> ()V keep
diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
index d8d563e..5cdb4f7 100644
--- a/ravenwood/texts/ravenwood-services-policies.txt
+++ b/ravenwood/texts/ravenwood-services-policies.txt
@@ -1,7 +1,7 @@
# Ravenwood "policy" file for services.core.
# Keep all AIDL interfaces
-class :aidl stubclass
+class :aidl keepclass
# Keep all feature flag implementations
-class :feature_flags stubclass
+class :feature_flags keepclass
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 82579d8..3f30b0a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -152,6 +152,16 @@
}
flag {
+ name: "remove_on_window_infos_changed_handler"
+ namespace: "accessibility"
+ description: "Updates onWindowInfosChanged() to run without posting to a handler."
+ bug: "333834990"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "reset_hover_event_timer_on_action_up"
namespace: "accessibility"
description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps."
@@ -183,3 +193,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_color_correction_saturation"
+ namespace: "accessibility"
+ description: "Feature allows users to change color correction saturation for daltonizer."
+ bug: "322829049"
+}
\ No newline at end of file
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 06a0297..71b16c3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -172,8 +172,7 @@
OnCrossProfileWidgetProvidersChangeListener {
private static final String TAG = "AppWidgetServiceImpl";
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -1573,9 +1572,36 @@
Binder.getCallingUid(), callingPackage);
if (widget != null && widget.provider != null && !widget.provider.zombie) {
- return cloneIfLocalBinder(widget.provider.getInfoLocked(mContext));
+ final AppWidgetProviderInfo info = widget.provider.getInfoLocked(mContext);
+ if (info == null) {
+ Slog.e(TAG, "getAppWidgetInfo() returns null because"
+ + " widget.provider.getInfoLocked() returned null."
+ + " appWidgetId=" + appWidgetId + " userId=" + userId
+ + " widget=" + widget);
+ return null;
+ }
+ final AppWidgetProviderInfo ret = cloneIfLocalBinder(info);
+ if (ret == null) {
+ Slog.e(TAG, "getAppWidgetInfo() returns null because"
+ + " cloneIfLocalBinder() returned null."
+ + " appWidgetId=" + appWidgetId + " userId=" + userId
+ + " widget=" + widget + " appWidgetProviderInfo=" + info);
+ }
+ return ret;
+ } else {
+ if (widget == null) {
+ Slog.e(TAG, "getAppWidgetInfo() returns null because widget is null."
+ + " appWidgetId=" + appWidgetId + " userId=" + userId);
+ } else if (widget.provider == null) {
+ Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is null."
+ + " appWidgetId=" + appWidgetId + " userId=" + userId
+ + " widget=" + widget);
+ } else {
+ Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is zombie."
+ + " appWidgetId=" + appWidgetId + " userId=" + userId
+ + " widget=" + widget);
+ }
}
-
return null;
}
}
@@ -2960,7 +2986,7 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = ri.activityInfo;
- if (DEBUG_NULL_PROVIDER_INFO) {
+ if (DEBUG) {
Objects.requireNonNull(ri.activityInfo);
}
return info;
@@ -2997,7 +3023,7 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = activityInfo;
- if (DEBUG_NULL_PROVIDER_INFO) {
+ if (DEBUG) {
Objects.requireNonNull(activityInfo);
}
@@ -3575,7 +3601,7 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
- if (DEBUG_NULL_PROVIDER_INFO) {
+ if (DEBUG) {
Objects.requireNonNull(providerInfo);
}
@@ -3594,7 +3620,7 @@
if (info != null) {
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
- if (DEBUG_NULL_PROVIDER_INFO) {
+ if (DEBUG) {
Objects.requireNonNull(providerInfo);
}
provider.setInfoLocked(info);
@@ -4678,6 +4704,9 @@
}
if (newInfo != null) {
info = newInfo;
+ if (DEBUG) {
+ Objects.requireNonNull(info);
+ }
updateGeneratedPreviewCategoriesLocked();
}
}
@@ -4699,12 +4728,18 @@
@GuardedBy("AppWidgetServiceImpl.mLock")
public void setPartialInfoLocked(AppWidgetProviderInfo info) {
this.info = info;
+ if (DEBUG) {
+ Objects.requireNonNull(this.info);
+ }
mInfoParsed = false;
}
@GuardedBy("AppWidgetServiceImpl.mLock")
public void setInfoLocked(AppWidgetProviderInfo info) {
this.info = info;
+ if (DEBUG) {
+ Objects.requireNonNull(this.info);
+ }
mInfoParsed = true;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index 468b9ab..219b788 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -36,6 +36,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
import com.android.server.autofill.ui.InlineFillUi;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -376,8 +377,8 @@
/**
* Internal implementation of {@link IInlineSuggestionsRequestCallback}.
*/
- private static final class InlineSuggestionsRequestCallbackImpl extends
- IInlineSuggestionsRequestCallback.Stub {
+ private static final class InlineSuggestionsRequestCallbackImpl
+ implements InlineSuggestionsRequestCallback {
private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
@@ -388,7 +389,7 @@
@BinderThread
@Override
- public void onInlineSuggestionsUnsupported() throws RemoteException {
+ public void onInlineSuggestionsUnsupported() {
if (sDebug) Slog.d(TAG, "onInlineSuggestionsUnsupported() called.");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
@@ -412,7 +413,7 @@
}
@Override
- public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+ public void onInputMethodStartInput(AutofillId imeFieldId) {
if (sVerbose) Slog.v(TAG, "onInputMethodStartInput() received on " + imeFieldId);
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
@@ -423,7 +424,7 @@
}
@Override
- public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+ public void onInputMethodShowInputRequested(boolean requestResult) {
if (sVerbose) {
Slog.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
}
@@ -454,7 +455,7 @@
}
@Override
- public void onInputMethodFinishInput() throws RemoteException {
+ public void onInputMethodFinishInput() {
if (sVerbose) Slog.v(TAG, "onInputMethodFinishInput() received");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
@@ -466,7 +467,7 @@
@BinderThread
@Override
- public void onInlineSuggestionsSessionInvalidated() throws RemoteException {
+ public void onInlineSuggestionsSessionInvalidated() {
if (sDebug) Slog.d(TAG, "onInlineSuggestionsSessionInvalidated() called.");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 07b16c5..a8b1235 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1168,6 +1168,7 @@
return null;
}
+ @GuardedBy("mLock")
@Nullable
private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) {
final ViewState state = mViewStates.get(autofillId);
@@ -1176,9 +1177,25 @@
return null;
}
AutofillValue value = state.getCurrentValue();
+
+ // Some app clears the form before navigating to another activities. In this case, use the
+ // cached value instead.
+ if (value == null || value.isEmpty()) {
+ AutofillValue candidateSaveValue = state.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sDebug) {
+ Slog.d(TAG, "findValueLocked(): current value for " + autofillId
+ + " is empty, using candidateSaveValue instead.");
+ }
+ return candidateSaveValue;
+ }
+ }
if (value == null) {
- if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId);
- value = getValueFromContextsLocked(autofillId);
+ if (sDebug) {
+ Slog.d(TAG, "findValueLocked(): no current value for " + autofillId
+ + ", checking value from previous fill contexts");
+ value = getValueFromContextsLocked(autofillId);
+ }
}
return value;
}
@@ -3717,19 +3734,34 @@
AutofillValue value = viewState.getCurrentValue();
if (value == null || value.isEmpty()) {
- final AutofillValue initialValue = getValueFromContextsLocked(id);
- if (initialValue != null) {
- if (sDebug) {
- Slog.d(TAG, "Value of required field " + id + " didn't change; "
- + "using initial value (" + initialValue + ") instead");
+ // Some apps clear the form before navigating to other activities.
+ // If current value is empty, consider fall back to last cached
+ // non-empty result first.
+ final AutofillValue candidateSaveValue =
+ viewState.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sVerbose) {
+ Slog.v(TAG, "current value is empty, using cached last non-empty "
+ + "value instead");
}
- value = initialValue;
+ value = candidateSaveValue;
} else {
- if (sDebug) {
- Slog.d(TAG, "empty value for required " + id );
+ // If candidate save value is also empty, consider falling back to initial
+ // value in context.
+ final AutofillValue initialValue = getValueFromContextsLocked(id);
+ if (initialValue != null) {
+ if (sDebug) {
+ Slog.d(TAG, "Value of required field " + id + " didn't change; "
+ + "using initial value (" + initialValue + ") instead");
+ }
+ value = initialValue;
+ } else {
+ if (sDebug) {
+ Slog.d(TAG, "empty value for required " + id);
+ }
+ allRequiredAreNotEmpty = false;
+ break;
}
- allRequiredAreNotEmpty = false;
- break;
}
}
@@ -3801,7 +3833,21 @@
continue;
}
if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
- final AutofillValue currentValue = viewState.getCurrentValue();
+ AutofillValue currentValue = viewState.getCurrentValue();
+ if (currentValue == null || currentValue.isEmpty()) {
+ // Some apps clear the form before navigating to other activities.
+ // If current value is empty, consider fall back to last cached
+ // non-empty result instead.
+ final AutofillValue candidateSaveValue =
+ viewState.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sVerbose) {
+ Slog.v(TAG, "current value is empty, using cached last "
+ + "non-empty value instead");
+ }
+ currentValue = candidateSaveValue;
+ }
+ }
final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue);
if (value == null) {
if (sDebug) {
@@ -4714,14 +4760,18 @@
@GuardedBy("mLock")
private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
ViewState viewState, int flags) {
+ // Cache the last non-empty value for save purpose. Some apps clear the form before
+ // navigating to other activities.
if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty())
&& viewState.getCurrentValue() != null && viewState.getCurrentValue().isText()
&& viewState.getCurrentValue().getTextValue() != null
&& viewState.getCurrentValue().getTextValue().length() > 1) {
if (sVerbose) {
- Slog.v(TAG, "Ignoring view state reset to empty on id " + id);
+ Slog.v(TAG, "value is resetting to empty, caching the last non-empty value");
}
- return;
+ viewState.setCandidateSaveValue(viewState.getCurrentValue());
+ } else {
+ viewState.setCandidateSaveValue(null);
}
final String textValue;
if (value == null || !value.isText()) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index fec5aa5..6ad0eb6 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -106,6 +106,15 @@
*/
private FillResponse mSecondaryFillResponse;
private AutofillValue mCurrentValue;
+
+ /**
+ * Some apps clear the form before navigating to another activity. The mCandidateSaveValue
+ * caches the value when a field with string longer than 2 characters are cleared.
+ *
+ * When showing save UI, if mCurrentValue of view state is empty, session would use
+ * mCandidateSaveValue to prompt save instead.
+ */
+ private AutofillValue mCandidateSaveValue;
private AutofillValue mAutofilledValue;
private AutofillValue mSanitizedValue;
private Rect mVirtualBounds;
@@ -139,6 +148,18 @@
mCurrentValue = value;
}
+ /**
+ * Gets the candidate save value of the view.
+ */
+ @Nullable
+ AutofillValue getCandidateSaveValue() {
+ return mCandidateSaveValue;
+ }
+
+ void setCandidateSaveValue(AutofillValue value) {
+ mCandidateSaveValue = value;
+ }
+
@Nullable
AutofillValue getAutofilledValue() {
return mAutofilledValue;
@@ -268,6 +289,9 @@
if (mCurrentValue != null) {
builder.append(", currentValue:" ).append(mCurrentValue);
}
+ if (mCandidateSaveValue != null) {
+ builder.append(", candidateSaveValue:").append(mCandidateSaveValue);
+ }
if (mAutofilledValue != null) {
builder.append(", autofilledValue:" ).append(mAutofilledValue);
}
@@ -302,6 +326,9 @@
if (mAutofilledValue != null) {
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
}
+ if (mCandidateSaveValue != null) {
+ pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue);
+ }
if (mSanitizedValue != null) {
pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
index 1009787..67ebb3e 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
@@ -658,11 +658,13 @@
* This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
* TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
*/
- private static class TransportConnectionMonitor implements ServiceConnection {
+ @VisibleForTesting
+ static class TransportConnectionMonitor implements ServiceConnection {
private final Context mContext;
private final WeakReference<TransportConnection> mTransportClientRef;
- private TransportConnectionMonitor(Context context,
+ @VisibleForTesting
+ TransportConnectionMonitor(Context context,
TransportConnection transportConnection) {
mContext = context;
mTransportClientRef = new WeakReference<>(transportConnection);
@@ -704,7 +706,13 @@
/** @see TransportConnection#finalize() */
private void referenceLost(String caller) {
- mContext.unbindService(this);
+ try {
+ mContext.unbindService(this);
+ } catch (IllegalArgumentException e) {
+ TransportUtils.log(Priority.WARN, TAG,
+ caller + " called but unbindService failed: " + e.getMessage());
+ return;
+ }
TransportUtils.log(
Priority.INFO,
TAG,
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9069689..026d29c 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -135,7 +135,7 @@
*/
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
@UserIdInt int userId, int associationId) {
- if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+ if (PackageUtils.isPermSyncAutoEnabled(mContext, mPackageManager, packageName)) {
Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
// Auto enable perm sync for the allowlisted packages, but don't override user decision
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 0e66fbc..71a1822 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -23,6 +23,7 @@
import android.os.Build;
import android.util.Slog;
+import com.google.security.cryptauth.lib.securegcm.ukey2.AlertException;
import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException;
import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException;
import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1;
@@ -203,7 +204,8 @@
*
* This method must only be called from one of the two participants.
*/
- public void establishSecureConnection() throws IOException, SecureChannelException {
+ public void establishSecureConnection() throws IOException,
+ SecureChannelException, HandshakeException {
if (isSecured()) {
Slog.d(TAG, "Channel is already secure.");
return;
@@ -334,7 +336,7 @@
}
}
- private void initiateHandshake() throws IOException, BadHandleException {
+ private void initiateHandshake() throws IOException, BadHandleException , HandshakeException {
if (mConnectionContext != null) {
Slog.d(TAG, "Ukey2 handshake is already completed.");
return;
@@ -394,8 +396,8 @@
}
}
- private void exchangeHandshake()
- throws IOException, HandshakeException, BadHandleException, CryptoException {
+ private void exchangeHandshake() throws IOException, HandshakeException,
+ BadHandleException, CryptoException, AlertException {
if (mConnectionContext != null) {
Slog.d(TAG, "Ukey2 handshake is already completed.");
return;
diff --git a/services/companion/java/com/android/server/companion/utils/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
index 254d28b..94ab9dd 100644
--- a/services/companion/java/com/android/server/companion/utils/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
@@ -21,6 +21,11 @@
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.os.Binder.getCallingUid;
+import static com.android.internal.R.array.config_companionDeviceCerts;
+import static com.android.internal.R.array.config_companionDevicePackages;
+import static com.android.internal.R.array.config_companionPermSyncEnabledCerts;
+import static com.android.internal.R.array.config_companionPermSyncEnabledPackages;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -185,15 +190,30 @@
*/
public static boolean isPackageAllowlisted(Context context,
PackageManagerInternal packageManagerInternal, @NonNull String packageName) {
- final String[] allowlistedPackages = context.getResources()
- .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+ return isPackageAllowlisted(context, packageManagerInternal, packageName,
+ config_companionDevicePackages, config_companionDeviceCerts);
+ }
+
+ /**
+ * Check if perm sync is allowlisted and auto-enabled for the package.
+ */
+ public static boolean isPermSyncAutoEnabled(Context context,
+ PackageManagerInternal packageManagerInternal, String packageName) {
+ return isPackageAllowlisted(context, packageManagerInternal, packageName,
+ config_companionPermSyncEnabledPackages, config_companionPermSyncEnabledCerts);
+ }
+
+ private static boolean isPackageAllowlisted(Context context,
+ PackageManagerInternal packageManagerInternal, String packageName,
+ int packagesConfig, int certsConfig) {
+ final String[] allowlistedPackages = context.getResources().getStringArray(packagesConfig);
if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
Slog.d(TAG, packageName + " is not allowlisted.");
return false;
}
final String[] allowlistedPackagesSignatureDigests = context.getResources()
- .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+ .getStringArray(certsConfig);
final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
for (int i = 0; i < allowlistedPackages.length; i++) {
if (allowlistedPackages[i].equals(packageName)) {
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 9a73a2d..f5db6e9 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -27,9 +27,9 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static com.android.server.contextualsearch.flags.Flags.enableExcludePersistentUi;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -286,13 +286,11 @@
}
final ScreenCapture.ScreenshotHardwareBuffer shb;
if (mWmInternal != null) {
- if (enableExcludePersistentUi()) {
- shb = mWmInternal.takeAssistScreenshot(
- Set.of(TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL));
- } else {
- shb = mWmInternal.takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
- }
-
+ shb = mWmInternal.takeAssistScreenshot(Set.of(
+ TYPE_STATUS_BAR,
+ TYPE_NAVIGATION_BAR,
+ TYPE_NAVIGATION_BAR_PANEL,
+ TYPE_POINTER));
} else {
shb = null;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d153c18..0fdf6d0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -232,6 +232,7 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
+ "audio-permission-aidl-java",
"cbor-java",
"com.android.media.audio-aconfig-java",
"icu4j_calendar_astronomer",
@@ -259,7 +260,6 @@
"connectivity_flags_lib",
"dreams_flags_lib",
"aconfig_new_storage_flags_lib",
- "aconfigd_java_proto_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index d04dbc2..d9e6186 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -870,6 +870,7 @@
}
synchronized (this) {
pinnedApp.mFiles.add(pf);
+ mPinnedFiles.put(pf.fileName, pf);
}
apkPinSizeLimit -= pf.bytesPinned;
@@ -1341,18 +1342,6 @@
public List<PinnedFileStat> getPinnerStats() {
ArrayList<PinnedFileStat> stats = new ArrayList<>();
- Collection<PinnedApp> pinnedApps;
- synchronized(this) {
- pinnedApps = mPinnedApps.values();
- }
- for (PinnedApp pinnedApp : pinnedApps) {
- for (PinnedFile pf : pinnedApp.mFiles) {
- PinnedFileStat stat =
- new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName);
- stats.add(stat);
- }
- }
-
Collection<PinnedFile> pinnedFiles;
synchronized(this) {
pinnedFiles = mPinnedFiles.values();
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5933639..04e85c7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -187,6 +187,20 @@
},
{
"name": "SelinuxFrameworksTests"
+ },
+ {
+ "name": "CtsWindowManagerBackgroundActivityTestCases",
+ "file_patterns": [
+ "Background.*\\.java",
+ "Activity.*\\.java"
+ ]
+ },
+ {
+ "name": "WmTests",
+ "file_patterns": [
+ "Background.*\\.java",
+ "Activity.*\\.java"
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 2812233..42391a5 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -24,7 +24,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
@@ -59,10 +58,10 @@
return;
}
if (DEBUG) Slog.d(TAG, "Set customized default_wallpaper.");
- Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
- // set a blank wallpaper to force a redraw of default_wallpaper
- wallpaperManager.setBitmap(blank);
- wallpaperManager.setResource(com.android.internal.R.drawable.default_wallpaper);
+ // Check if it is not a live wallpaper set
+ if (wallpaperManager.getWallpaperInfo() == null) {
+ wallpaperManager.clearWallpaper();
+ }
} catch (Exception e) {
Slog.w(TAG, "Failed to customize system wallpaper." + e);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 94bf813..9be0e1f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3783,6 +3783,14 @@
return fgsInfo.getLastFgsStartTime() + Math.max(0, timeLimit - fgsInfo.getTotalRuntime());
}
+ private TimeLimitedFgsInfo getFgsTimeLimitedInfo(int uid, int fgsType) {
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(uid);
+ if (fgsInfo != null) {
+ return fgsInfo.get(fgsType);
+ }
+ return null;
+ }
+
private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, int previousFgsType) {
final int previouslyTimeLimitedType = getTimeLimitedFgsType(previousFgsType);
if (previouslyTimeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
@@ -3793,16 +3801,12 @@
if (previouslyTimeLimitedType != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
// FGS is switching types and the previous type was time-limited so update the runtime.
- final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
- if (fgsInfo != null) {
- final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(previouslyTimeLimitedType);
- if (fgsTypeInfo != null) {
- // Update the total runtime for the previous time-limited fgs type.
- fgsTypeInfo.updateTotalRuntime();
- // TODO(b/330399444): handle the case where an app is running 2 services of the
- // same time-limited type in parallel and stops one of them which leads to the
- // second running one gaining additional runtime.
- }
+ final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(
+ sr.appInfo.uid, previouslyTimeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis());
+ fgsTypeInfo.decNumParallelServices();
}
if (!sr.isFgsTimeLimited()) {
@@ -3825,10 +3829,10 @@
final int timeLimitedFgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
if (fgsTypeInfo == null) {
- fgsTypeInfo = sr.createTimeLimitedFgsInfo(nowUptime);
+ fgsTypeInfo = sr.createTimeLimitedFgsInfo();
fgsInfo.put(timeLimitedFgsType, fgsTypeInfo);
}
- fgsTypeInfo.setLastFgsStartTime(nowUptime);
+ fgsTypeInfo.noteFgsFgsStart(nowUptime);
// We'll cancel the previous ANR timer and start a fresh one below.
mFGSAnrTimer.cancel(sr);
@@ -3852,13 +3856,12 @@
return; // if the current fgs type is not time-limited, return.
}
- final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
- if (fgsInfo != null) {
- final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedType);
- if (fgsTypeInfo != null) {
- // Update the total runtime for the previous time-limited fgs type.
- fgsTypeInfo.updateTotalRuntime();
- }
+ final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(
+ sr.appInfo.uid, timeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis());
+ fgsTypeInfo.decNumParallelServices();
}
Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
mFGSAnrTimer.cancel(sr);
@@ -3913,24 +3916,21 @@
mFGSAnrTimer.accept(sr);
traceInstant("FGS timed out: ", sr);
- final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
- if (fgsInfo != null) {
- final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(fgsType);
- if (fgsTypeInfo != null) {
- // Update total runtime for the time-limited fgs type and mark it as timed out.
- fgsTypeInfo.updateTotalRuntime();
- fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
+ final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType);
+ if (fgsTypeInfo != null) {
+ // Update total runtime for the time-limited fgs type and mark it as timed out.
+ fgsTypeInfo.updateTotalRuntime(nowUptime);
+ fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
- logFGSStateChangeLocked(sr,
- FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
- nowUptime > fgsTypeInfo.getLastFgsStartTime()
- ? (int) (nowUptime - fgsTypeInfo.getLastFgsStartTime()) : 0,
- FGS_STOP_REASON_UNKNOWN,
- FGS_TYPE_POLICY_CHECK_UNKNOWN,
- FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
- false /* fgsRestrictionRecalculated */
- );
- }
+ logFGSStateChangeLocked(sr,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+ nowUptime > fgsTypeInfo.getFirstFgsStartUptime()
+ ? (int) (nowUptime - fgsTypeInfo.getFirstFgsStartUptime()) : 0,
+ FGS_STOP_REASON_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+ false /* fgsRestrictionRecalculated */
+ );
}
try {
@@ -3949,6 +3949,16 @@
if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
return; // no timed out FGS type was found (either it was stopped or it switched types)
}
+
+ synchronized (mAm) {
+ final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType);
+ if (fgsTypeInfo != null) {
+ // Runtime is already updated when the service times out - if the app didn't
+ // stop the service, decrement the number of parallel running services here.
+ fgsTypeInfo.decNumParallelServices();
+ }
+ }
+
final String reason = "A foreground service of type "
+ ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ " did not stop within its timeout: " + sr.getComponentName();
@@ -3958,7 +3968,6 @@
Slog.wtf(TAG, reason);
return;
}
-
if (android.app.Flags.enableFgsTimeoutCrashBehavior()) {
// Crash the app
synchronized (mAm) {
@@ -4005,7 +4014,6 @@
private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
maybeStopShortFgsTimeoutLocked(service);
- maybeStopFgsTimeoutLocked(service);
final ProcessServiceRecord psr = service.app.mServices;
psr.stopService(service);
psr.updateBoundClientUids();
@@ -6291,7 +6299,6 @@
Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
maybeStopShortFgsTimeoutLocked(r);
}
- maybeStopFgsTimeoutLocked(r);
// Report to all of the connections that the service is no longer
// available.
@@ -6416,7 +6423,6 @@
final boolean exitingFg = r.isForeground;
if (exitingFg) {
maybeStopShortFgsTimeoutLocked(r);
- maybeStopFgsTimeoutLocked(r);
decActiveForegroundAppLocked(smap, r);
synchronized (mAm.mProcessStats.mLock) {
ServiceState stracker = r.getTracker();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 46ed1fd..00d8efa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -598,6 +598,9 @@
// as one line, but close enough for now.
static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
+ // How many seconds should the system wait before terminating the spawned logcat process.
+ static final int LOGCAT_TIMEOUT_SEC = 10;
+
// Necessary ApplicationInfo flags to mark an app as persistent
static final int PERSISTENT_MASK =
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT;
@@ -9939,126 +9942,70 @@
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
final boolean runSynchronously = process == null;
- Thread worker =
- new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- if (report != null) {
- sb.append(report);
+ Thread worker = new Thread("Error dump: " + dropboxTag) {
+ @Override
+ public void run() {
+ if (report != null) {
+ sb.append(report);
+ }
+
+ String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+ String kerLogSetting = Settings.Global.ERROR_KERNEL_LOG_PREFIX + dropboxTag;
+ String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+ int logcatLines = Build.IS_USER
+ ? 0
+ : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+ int kernelLogLines = Build.IS_USER
+ ? 0
+ : Settings.Global.getInt(mContext.getContentResolver(), kerLogSetting, 0);
+ int dropboxMaxSize = Settings.Global.getInt(
+ mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
+
+ if (dataFile != null) {
+ // Attach the stack traces file to the report so collectors can load them
+ // by file if they have access.
+ sb.append(DATA_FILE_PATH_HEADER)
+ .append(dataFile.getAbsolutePath()).append('\n');
+
+ int maxDataFileSize = dropboxMaxSize
+ - sb.length()
+ - logcatLines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - kernelLogLines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - DATA_FILE_PATH_FOOTER.length();
+
+ if (maxDataFileSize > 0) {
+ // Inline dataFile contents if there is room.
+ try {
+ sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
+ "\n\n[[TRUNCATED]]\n"));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading " + dataFile, e);
}
-
- String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
- String maxBytesSetting =
- Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
- int lines =
- Build.IS_USER
- ? 0
- : Settings.Global.getInt(
- mContext.getContentResolver(), logcatSetting, 0);
- int dropboxMaxSize =
- Settings.Global.getInt(
- mContext.getContentResolver(),
- maxBytesSetting,
- DROPBOX_DEFAULT_MAX_SIZE);
-
- if (dataFile != null) {
- // Attach the stack traces file to the report so collectors can load
- // them
- // by file if they have access.
- sb.append(DATA_FILE_PATH_HEADER)
- .append(dataFile.getAbsolutePath())
- .append('\n');
-
- int maxDataFileSize =
- dropboxMaxSize
- - sb.length()
- - lines * RESERVED_BYTES_PER_LOGCAT_LINE
- - DATA_FILE_PATH_FOOTER.length();
-
- if (maxDataFileSize > 0) {
- // Inline dataFile contents if there is room.
- try {
- sb.append(
- FileUtils.readTextFile(
- dataFile,
- maxDataFileSize,
- "\n\n[[TRUNCATED]]\n"));
- } catch (IOException e) {
- Slog.e(TAG, "Error reading " + dataFile, e);
- }
- }
-
- // Always append the footer, even there wasn't enough space to inline
- // the
- // dataFile contents.
- sb.append(DATA_FILE_PATH_FOOTER);
- }
-
- if (crashInfo != null && crashInfo.stackTrace != null) {
- sb.append(crashInfo.stackTrace);
- }
-
- if (lines > 0 && !runSynchronously) {
- sb.append("\n");
-
- InputStreamReader input = null;
- try {
- java.lang.Process logcat =
- new ProcessBuilder(
- // Time out after 10s of inactivity, but
- // kill logcat with SEGV
- // so we can investigate why it didn't
- // finish.
- "/system/bin/timeout",
- "-i",
- "-s",
- "SEGV",
- "10s",
- // Merge several logcat streams, and take
- // the last N lines.
- "/system/bin/logcat",
- "-v",
- "threadtime",
- "-b",
- "events",
- "-b",
- "system",
- "-b",
- "main",
- "-b",
- "crash",
- "-t",
- String.valueOf(lines))
- .redirectErrorStream(true)
- .start();
-
- try {
- logcat.getOutputStream().close();
- } catch (IOException e) {
- }
- try {
- logcat.getErrorStream().close();
- } catch (IOException e) {
- }
- input = new InputStreamReader(logcat.getInputStream());
-
- int num;
- char[] buf = new char[8192];
- while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
- } catch (IOException e) {
- Slog.e(TAG, "Error running logcat", e);
- } finally {
- if (input != null)
- try {
- input.close();
- } catch (IOException e) {
- }
- }
- }
-
- dbox.addText(dropboxTag, sb.toString());
}
- };
+ // Always append the footer, even there wasn't enough space to inline the
+ // dataFile contents.
+ sb.append(DATA_FILE_PATH_FOOTER);
+ }
+
+ if (crashInfo != null && crashInfo.stackTrace != null) {
+ sb.append(crashInfo.stackTrace);
+ }
+ boolean shouldAddLogs = logcatLines > 0 || kernelLogLines > 0;
+ if (!runSynchronously && shouldAddLogs) {
+ sb.append("\n");
+ if (logcatLines > 0) {
+ fetchLogcatBuffers(sb, logcatLines, LOGCAT_TIMEOUT_SEC,
+ List.of("events", "system", "main", "crash"));
+ }
+ if (kernelLogLines > 0) {
+ fetchLogcatBuffers(sb, kernelLogLines, LOGCAT_TIMEOUT_SEC / 2,
+ List.of("kernel"));
+ }
+ }
+
+ dbox.addText(dropboxTag, sb.toString());
+ }
+ };
if (runSynchronously) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10321,6 +10268,67 @@
}
/**
+ * Retrieves logs from specified logcat buffers and appends them to a StringBuilder
+ * in the supplied order. The method executes a logcat command to fetch specific
+ * log entries from the supplied buffers.
+ *
+ * @param sb the StringBuilder to append the logcat output to.
+ * @param lines the number of lines to retrieve.
+ * @param timeout the maximum allowed time in seconds for logcat to run before being terminated.
+ * @param buffers the list of log buffers from which to retrieve logs.
+ */
+ private static void fetchLogcatBuffers(StringBuilder sb, int lines,
+ int timeout, List<String> buffers) {
+
+ if (buffers.size() == 0 || lines <= 0 || timeout <= 0) {
+ return;
+ }
+
+ List<String> command = new ArrayList<>(10 + (2 * buffers.size()));
+ // Time out after 10s of inactivity, but kill logcat with SEGV
+ // so we can investigate why it didn't finish.
+ command.add("/system/bin/timeout");
+ command.add("-i");
+ command.add("-s");
+ command.add("SEGV");
+ command.add(timeout + "s");
+
+ // Merge several logcat streams, and take the last N lines.
+ command.add("/system/bin/logcat");
+ command.add("-v");
+ // This adds a timestamp and thread info to each log line.
+ command.add("threadtime");
+ for (String buffer : buffers) {
+ command.add("-b");
+ command.add(buffer);
+ }
+ // Limit the output to the last N lines.
+ command.add("-t");
+ command.add(String.valueOf(lines));
+
+ try {
+ java.lang.Process proc =
+ new ProcessBuilder(command).redirectErrorStream(true).start();
+
+ // Close the output stream immediately as we do not send input to the process.
+ try {
+ proc.getOutputStream().close();
+ } catch (IOException e) {
+ }
+
+ try (InputStreamReader reader = new InputStreamReader(proc.getInputStream())) {
+ char[] buffer = new char[8192];
+ int numRead;
+ while ((numRead = reader.read(buffer, 0, buffer.length)) > 0) {
+ sb.append(buffer, 0, numRead);
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running logcat", e);
+ }
+ }
+
+ /**
* Check if the calling process has the permission to dump given package,
* throw SecurityException if it doesn't have the permission.
*
@@ -12326,8 +12334,8 @@
ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ,
ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ,
ProcessList.VISIBLE_APP_ADJ,
- ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
- ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ,
+ ProcessList.PERCEPTIBLE_APP_ADJ,
+ ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ,
ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ,
ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ
@@ -12335,7 +12343,7 @@
static final String[] DUMP_MEM_OOM_LABEL = new String[] {
"Native",
"System", "Persistent", "Persistent Service", "Foreground",
- "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium",
+ "Visible", "Perceptible", "Perceptible Medium", "Perceptible Low",
"Backup", "Heavy Weight",
"A Services", "Home",
"Previous", "B Services", "Cached"
@@ -12343,7 +12351,7 @@
static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] {
"native",
"sys", "pers", "persvc", "fore",
- "vis", "percept", "perceptl", "perceptm",
+ "vis", "percept", "perceptm", "perceptl",
"backup", "heavy",
"servicea", "home",
"prev", "serviceb", "cached"
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a182a10..bbd4323 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -130,6 +130,7 @@
import com.android.server.am.nano.Capability;
import com.android.server.am.nano.FrameworkCapability;
import com.android.server.am.nano.VMCapability;
+import com.android.server.am.nano.VMInfo;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
@@ -460,6 +461,8 @@
return -1;
}
}
+ String vmName = System.getProperty("java.vm.name", "?");
+ String vmVersion = System.getProperty("java.vm.version", "?");
if (outputAsProtobuf) {
Capabilities capabilities = new Capabilities();
@@ -486,6 +489,11 @@
capabilities.frameworkCapabilities[i] = cap;
}
+ VMInfo vmInfo = new VMInfo();
+ vmInfo.name = vmName;
+ vmInfo.version = vmVersion;
+ capabilities.vmInfo = vmInfo;
+
try {
getRawOutputStream().write(Capabilities.toByteArray(capabilities));
} catch (IOException e) {
@@ -505,6 +513,8 @@
for (String capability : Debug.getFeatureList()) {
pw.println("framework:" + capability);
}
+ pw.println("vm_name:" + vmName);
+ pw.println("vm_version:" + vmVersion);
}
return 0;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4f84149..58732fd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -159,6 +159,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* All information we are collecting about things that can happen that impact
@@ -409,26 +411,14 @@
com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
- final long powerStatsThrottlePeriodCpu = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu);
- final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio);
- final long powerStatsThrottlePeriodWifi = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi);
- mBatteryStatsConfig =
+ BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder =
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
- .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_CPU,
- powerStatsThrottlePeriodCpu)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- powerStatsThrottlePeriodMobileRadio)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_WIFI,
- powerStatsThrottlePeriodWifi)
- .build();
+ .setResetOnUnplugAfterSignificantCharge(
+ resetOnUnplugAfterSignificantCharge);
+ setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString(
+ com.android.internal.R.string.config_powerStatsThrottlePeriods));
+ mBatteryStatsConfig = batteryStatsConfigBuilder.build();
mPowerStatsUidResolver = new PowerStatsUidResolver();
mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
@@ -515,6 +505,26 @@
return config;
}
+ private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
+ String configString) {
+ Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString);
+ while (matcher.find()) {
+ String powerComponentName = matcher.group(1);
+ long throttlePeriod;
+ try {
+ throttlePeriod = Long.parseLong(matcher.group(2));
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid config_powerStatsThrottlePeriods format: " + configString);
+ }
+ if (powerComponentName.equals("*")) {
+ builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod);
+ } else {
+ builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod);
+ }
+ }
+ }
+
/**
* Creates an instance of BatteryStatsService and restores data from stored state.
*/
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index da45a77..8d7a1c9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,10 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -389,13 +393,20 @@
private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
@Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
- if (options == null || !options.containsKey(
- ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
+ if (options == null) {
return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
- return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
- ? BackgroundStartPrivileges.ALLOW_BAL
- : BackgroundStartPrivileges.NONE;
+ switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) {
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED:
+ return BackgroundStartPrivileges.NONE;
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ default:
+ return BackgroundStartPrivileges.ALLOW_BAL;
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8eca4fc..2184340 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -690,10 +690,14 @@
private long mTimeLimitExceededAt = Long.MIN_VALUE;
@UptimeMillisLong
private long mTotalRuntime = 0;
+ private int mNumParallelServices = 0;
- TimeLimitedFgsInfo(@UptimeMillisLong long startTime) {
- mFirstFgsStartUptime = startTime;
- mFirstFgsStartRealtime = SystemClock.elapsedRealtime();
+ public void noteFgsFgsStart(@UptimeMillisLong long startTime) {
+ mNumParallelServices++;
+ if (mNumParallelServices == 1) {
+ mFirstFgsStartUptime = startTime;
+ mFirstFgsStartRealtime = SystemClock.elapsedRealtime();
+ }
mLastFgsStartTime = startTime;
}
@@ -707,17 +711,23 @@
return mFirstFgsStartRealtime;
}
- public void setLastFgsStartTime(@UptimeMillisLong long startTime) {
- mLastFgsStartTime = startTime;
- }
-
@UptimeMillisLong
public long getLastFgsStartTime() {
return mLastFgsStartTime;
}
- public void updateTotalRuntime() {
- mTotalRuntime += SystemClock.uptimeMillis() - mLastFgsStartTime;
+ public void decNumParallelServices() {
+ if (mNumParallelServices > 0) {
+ mNumParallelServices--;
+ }
+ if (mNumParallelServices == 0) {
+ mLastFgsStartTime = 0;
+ }
+ }
+
+ public void updateTotalRuntime(@UptimeMillisLong long nowUptime) {
+ mTotalRuntime += nowUptime - mLastFgsStartTime;
+ mLastFgsStartTime = nowUptime;
}
@UptimeMillisLong
@@ -735,6 +745,7 @@
}
public void reset() {
+ mNumParallelServices = 0;
mFirstFgsStartUptime = 0;
mFirstFgsStartRealtime = 0;
mLastFgsStartTime = 0;
@@ -1872,8 +1883,8 @@
/**
* Called when a time-limited FGS starts.
*/
- public TimeLimitedFgsInfo createTimeLimitedFgsInfo(@UptimeMillisLong long nowUptime) {
- return new TimeLimitedFgsInfo(nowUptime);
+ public TimeLimitedFgsInfo createTimeLimitedFgsInfo() {
+ return new TimeLimitedFgsInfo();
}
/**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 827db57..5793758 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -29,6 +29,8 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -262,11 +264,11 @@
Uri settingUri = Settings.Global.getUriFor(globalSetting);
String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
if (settingUri == null) {
- log("setting uri is null for globalSetting " + globalSetting);
+ logErr("setting uri is null for globalSetting " + globalSetting);
continue;
}
if (propName == null) {
- log("invalid prop name for globalSetting " + globalSetting);
+ logErr("invalid prop name for globalSetting " + globalSetting);
continue;
}
@@ -294,7 +296,7 @@
for (String key : properties.getKeyset()) {
String propertyName = makePropertyName(scope, key);
if (propertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -306,7 +308,7 @@
// sys prop slot can be removed.
String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -324,7 +326,7 @@
for (String key : properties.getKeyset()) {
String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -354,7 +356,7 @@
if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
|| propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- log("unable to construct system property for " + actualNamespace
+ logErr("unable to construct system property for " + actualNamespace
+ "/" + flagName);
continue;
}
@@ -383,18 +385,18 @@
/**
* apply flag local override in aconfig new storage
- * @param props
- * @return aconfigd socket return
+ * @param requests: request proto output stream
+ * @return aconfigd socket return as proto input stream
*/
- public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) {
+ static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
// connect to aconfigd socket
LocalSocket client = new LocalSocket();
try{
client.connect(new LocalSocketAddress(
"aconfigd", LocalSocketAddress.Namespace.RESERVED));
- log("connected to aconfigd socket");
+ Slog.d(TAG, "connected to aconfigd socket");
} catch (IOException ioe) {
- log("failed to connect to aconfigd socket", ioe);
+ logErr("failed to connect to aconfigd socket", ioe);
return null;
}
@@ -404,43 +406,93 @@
inputStream = new DataInputStream(client.getInputStream());
outputStream = new DataOutputStream(client.getOutputStream());
} catch (IOException ioe) {
- log("failed to get local socket iostreams", ioe);
+ logErr("failed to get local socket iostreams", ioe);
return null;
}
// send requests
try {
- byte[] requests_bytes = requests.toByteArray();
+ byte[] requests_bytes = requests.getBytes();
outputStream.writeInt(requests_bytes.length);
outputStream.write(requests_bytes, 0, requests_bytes.length);
- log(requests.getMsgsCount() + " flag override requests sent to aconfigd");
+ Slog.d(TAG, "flag override requests sent to aconfigd");
} catch (IOException ioe) {
- log("failed to send requests to aconfigd", ioe);
+ logErr("failed to send requests to aconfigd", ioe);
return null;
}
// read return
- StorageReturnMessages return_msgs = null;
try {
int num_bytes = inputStream.readInt();
- byte[] buffer = new byte[num_bytes];
- inputStream.read(buffer, 0, num_bytes);
- return_msgs = StorageReturnMessages.parseFrom(buffer);
- log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd");
+ ProtoInputStream returns = new ProtoInputStream(inputStream);
+ Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+ return returns;
} catch (IOException ioe) {
- log("failed to read requests return from aconfigd", ioe);
+ logErr("failed to read requests return from aconfigd", ioe);
return null;
}
+ }
- return return_msgs;
+ /**
+ * serialize a flag override request
+ * @param proto
+ */
+ static void writeFlagOverrideRequest(
+ ProtoOutputStream proto, String packageName, String flagName, String flagValue,
+ boolean isLocal) {
+ long msgsToken = proto.start(StorageRequestMessages.MSGS);
+ long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
+ proto.end(msgToken);
+ proto.end(msgsToken);
+ }
+
+ /**
+ * deserialize a flag input proto stream and log
+ * @param proto
+ */
+ static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException {
+ while (true) {
+ switch (proto.nextField()) {
+ case (int) StorageReturnMessages.MSGS:
+ long msgsToken = proto.start(StorageReturnMessages.MSGS);
+ switch (proto.nextField()) {
+ case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
+ Slog.d(TAG, "successfully handled override requests");
+ long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
+ proto.end(msgToken);
+ break;
+ case (int) StorageReturnMessage.ERROR_MESSAGE:
+ String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
+ Slog.d(TAG, "override request failed: " + errmsg);
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ break;
+ default:
+ logErr("invalid message type, expecting only flag override return or error message");
+ break;
+ }
+ proto.end(msgsToken);
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return;
+ default:
+ logErr("invalid message type, expect storage return message");
+ break;
+ }
+ }
}
/**
* apply flag local override in aconfig new storage
* @param props
*/
- public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
- StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+ int num_requests = 0;
+ ProtoOutputStream requests = new ProtoOutputStream();
for (String flagName : props.getKeyset()) {
String flagValue = props.getString(flagName, null);
if (flagName == null || flagValue == null) {
@@ -449,32 +501,35 @@
int idx = flagName.indexOf(":");
if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid local flag override: " + flagName);
+ logErr("invalid local flag override: " + flagName);
continue;
}
String actualNamespace = flagName.substring(0, idx);
String fullFlagName = flagName.substring(idx+1);
idx = fullFlagName.lastIndexOf(".");
if (idx == -1) {
- log("invalid flag name: " + fullFlagName);
+ logErr("invalid flag name: " + fullFlagName);
continue;
}
String packageName = fullFlagName.substring(0, idx);
String realFlagName = fullFlagName.substring(idx+1);
-
- StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
- StorageRequestMessage.FlagOverrideMessage.newBuilder();
- override_msg_builder.setPackageName(packageName);
- override_msg_builder.setFlagName(realFlagName);
- override_msg_builder.setFlagValue(flagValue);
- override_msg_builder.setIsLocal(true);
-
- StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
- request_builder.setFlagOverrideMessage(override_msg_builder.build());
- requests_builder.addMsgs(request_builder.build());
+ writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+ ++num_requests;
}
- StorageRequestMessages requests = requests_builder.build();
- StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ // send requests to aconfigd and obtain the return byte buffer
+ ProtoInputStream returns = sendAconfigdRequests(requests);
+
+ // deserialize back using proto input stream
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -517,7 +572,7 @@
for (String property_name : property_names) {
String[] segments = property_name.split("\\.");
if (segments.length < 3) {
- log("failed to extract category name from property " + property_name);
+ logErr("failed to extract category name from property " + property_name);
continue;
}
categories.add(segments[2]);
@@ -545,14 +600,16 @@
return propertyName;
}
+
/**
* stage flags in aconfig new storage
* @param propsToStage
*/
@VisibleForTesting
static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
- // create storage request proto
- StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ // write aconfigd requests proto to proto output stream
+ int num_requests = 0;
+ ProtoOutputStream requests = new ProtoOutputStream();
for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
String actualNamespace = entry.getKey();
HashMap<String, String> flagValuesToStage = entry.getValue();
@@ -560,26 +617,29 @@
String stagedValue = flagValuesToStage.get(fullFlagName);
int idx = fullFlagName.lastIndexOf(".");
if (idx == -1) {
- log("invalid flag name: " + fullFlagName);
+ logErr("invalid flag name: " + fullFlagName);
continue;
}
String packageName = fullFlagName.substring(0, idx);
String flagName = fullFlagName.substring(idx+1);
-
- StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
- StorageRequestMessage.FlagOverrideMessage.newBuilder();
- override_msg_builder.setPackageName(packageName);
- override_msg_builder.setFlagName(flagName);
- override_msg_builder.setFlagValue(stagedValue);
- override_msg_builder.setIsLocal(false);
-
- StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
- request_builder.setFlagOverrideMessage(override_msg_builder.build());
- requests_builder.addMsgs(request_builder.build());
+ writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
+ ++num_requests;
}
}
- StorageRequestMessages requests = requests_builder.build();
- StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ // send requests to aconfigd and obtain the return
+ ProtoInputStream returns = sendAconfigdRequests(requests);
+
+ // deserialize back using proto input stream
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
}
/**
@@ -620,7 +680,7 @@
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);
+ logErr("invalid staged flag: " + flagName);
continue;
}
String actualNamespace = flagName.substring(0, idx);
@@ -671,7 +731,7 @@
}
value = "";
} else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
- log("key=" + key + " value=" + value + " exceeds system property max length.");
+ logErr("key=" + key + " value=" + value + " exceeds system property max length.");
return;
}
@@ -681,11 +741,11 @@
// Failure to set a property can be caused by SELinux denial. This usually indicates
// that the property wasn't allowlisted in sepolicy.
// No need to report it on all user devices, only on debug builds.
- log("Unable to set property " + key + " value '" + value + "'", e);
+ logErr("Unable to set property " + key + " value '" + value + "'", e);
}
}
- private static void log(String msg, Exception e) {
+ private static void logErr(String msg, Exception e) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, msg, e);
} else {
@@ -693,7 +753,7 @@
}
}
- private static void log(String msg) {
+ private static void logErr(String msg) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, msg);
} else {
@@ -711,7 +771,7 @@
br.close();
} catch (IOException ioe) {
- log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+ logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
}
return content;
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1db3483..ad93f6f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -55,6 +55,7 @@
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.extractFlagsFromKey;
@@ -70,7 +71,6 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -130,6 +130,7 @@
import android.os.UserHandle;
import android.os.storage.StorageManagerInternal;
import android.permission.PermissionManager;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -140,6 +141,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;
@@ -153,7 +155,6 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.camera.flags.Flags;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.Clock;
import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -1421,6 +1422,9 @@
// The callback method from AppOpsUidStateTracker
private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
synchronized (this) {
+ if (state == UID_STATE_NONEXISTENT) {
+ onUidProcessDeathLocked(uid);
+ }
UidState uidState = getUidStateLocked(uid, false);
boolean hasForegroundWatchers = false;
@@ -1508,6 +1512,11 @@
}
}
+ if (state == UID_STATE_NONEXISTENT) {
+ // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps
+ return;
+ }
+
if (uidState != null) {
int numPkgs = uidState.pkgOps.size();
for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
@@ -1532,6 +1541,81 @@
}
}
+ @GuardedBy("this")
+ private void onUidProcessDeathLocked(int uid) {
+ if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) {
+ return;
+ }
+ final SparseLongArray chainsToFinish = new SparseLongArray();
+ doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> {
+ attributedOp.doForAllInProgressStartOpEvents((event) -> {
+ int chainId = event.getAttributionChainId();
+ if (chainId != ATTRIBUTION_CHAIN_ID_NONE) {
+ long currentEarliestStartTime =
+ chainsToFinish.get(chainId, Long.MAX_VALUE);
+ if (event.getStartTime() < currentEarliestStartTime) {
+ // Store the earliest chain link we're finishing, so that we can go back
+ // and finish any links in the chain that started after this one
+ chainsToFinish.put(chainId, event.getStartTime());
+ }
+ }
+ attributedOp.finished(event.getClientId());
+ });
+ });
+ finishChainsLocked(chainsToFinish);
+ }
+
+ @GuardedBy("this")
+ private void finishChainsLocked(SparseLongArray chainsToFinish) {
+ doForAllAttributedOpsLocked((attributedOp) -> {
+ attributedOp.doForAllInProgressStartOpEvents((event) -> {
+ int chainId = event.getAttributionChainId();
+ // If this event is part of a chain, and this event started after the event in the
+ // chain we already finished, then finish this event, too
+ long earliestEventStart = chainsToFinish.get(chainId, Long.MAX_VALUE);
+ if (chainId != ATTRIBUTION_CHAIN_ID_NONE
+ && event.getStartTime() >= earliestEventStart) {
+ attributedOp.finished(event.getClientId());
+ }
+ });
+ });
+ }
+
+ @GuardedBy("this")
+ private void doForAllAttributedOpsLocked(Consumer<AttributedOp> action) {
+ int numUids = mUidStates.size();
+ for (int uidNum = 0; uidNum < numUids; uidNum++) {
+ int uid = mUidStates.keyAt(uidNum);
+ doForAllAttributedOpsInUidLocked(uid, action);
+ }
+ }
+
+ @GuardedBy("this")
+ private void doForAllAttributedOpsInUidLocked(int uid, Consumer<AttributedOp> action) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+ int numDevices = op.mDeviceAttributedOps.size();
+ for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) {
+ ArrayMap<String, AttributedOp> attrOps =
+ op.mDeviceAttributedOps.valueAt(deviceNum);
+ int numAttributions = attrOps.size();
+ for (int attrNum = 0; attrNum < numAttributions; attrNum++) {
+ action.accept(attrOps.valueAt(attrNum));
+ }
+ }
+ }
+ }
+ }
+
/**
* Notify the proc state or capability has changed for a certain UID.
*/
@@ -2702,7 +2786,7 @@
* have information on them.
*/
private static boolean isOpAllowedForUid(int uid) {
- return runtimePermissionAppopsMappingEnabled()
+ return Flags.runtimePermissionAppopsMappingEnabled()
&& (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
}
@@ -4775,8 +4859,8 @@
if ((code == OP_CAMERA) && isAutomotive()) {
final long identity = Binder.clearCallingIdentity();
try {
- if ((Flags.cameraPrivacyAllowlist())
- && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
+ if (com.android.internal.camera.flags.Flags.cameraPrivacyAllowlist()
+ && mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) {
return true;
}
} finally {
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 18ea8cf..268b286 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -68,6 +68,7 @@
return UID_STATE_BACKGROUND;
}
+ // UID_STATE_NONEXISTENT is deliberately excluded here
return UID_STATE_CACHED;
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index bc6ef20..03c8156 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -34,7 +34,9 @@
import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.finishRunningOpsForKilledPackages;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
@@ -343,13 +345,14 @@
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
+ boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ || capability != pendingCapability
+ || appWidgetVisible != pendingAppWidgetVisible;
+
if (uidState != pendingUidState
|| capability != pendingCapability
|| appWidgetVisible != pendingAppWidgetVisible) {
- boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- || capability != pendingCapability
- || appWidgetVisible != pendingAppWidgetVisible;
if (foregroundChange) {
// To save on memory usage, log only interesting changes.
@@ -372,6 +375,16 @@
mCapability.delete(uid);
mAppWidgetVisible.delete(uid);
mPendingGone.delete(uid);
+ if (finishRunningOpsForKilledPackages()) {
+ for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
+ UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
+ Executor executor = mUidStateChangedCallbacks.valueAt(i);
+
+ executor.execute(PooledLambda.obtainRunnable(
+ UidStateChangedCallback::onUidStateChanged, cb, uid,
+ UID_STATE_NONEXISTENT, foregroundChange));
+ }
+ }
} else {
mUidStates.put(uid, pendingUidState);
mCapability.put(uid, pendingCapability);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2760ccf..02fc993 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.function.Consumer;
final class AttributedOp {
private final @NonNull AppOpsService mAppOpsService;
@@ -256,6 +257,19 @@
}
}
+ public void doForAllInProgressStartOpEvents(Consumer<InProgressStartOpEvent> action) {
+ ArrayMap<IBinder, AttributedOp.InProgressStartOpEvent> events = isPaused()
+ ? mPausedInProgressEvents : mInProgressEvents;
+ if (events == null) {
+ return;
+ }
+
+ int numStartedOps = events.size();
+ for (int i = 0; i < numStartedOps; i++) {
+ action.accept(events.valueAt(i));
+ }
+ }
+
/**
* Update state when finishOp was called. Will finish started ops, and delete paused ops.
*
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c310822..15c5c10 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4440,7 +4440,8 @@
|| usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
voiceActive = true;
}
- if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+ if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME
+ || usage == AudioAttributes.USAGE_UNKNOWN) {
mediaActive = true;
}
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e2c4b46..cae1695 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -347,9 +347,6 @@
//------------------------------------------------------
// routing monitoring
synchronized void onRoutingUpdated() {
- if (!mFeatureEnabled) {
- return;
- }
switch (mState) {
case STATE_UNINITIALIZED:
case STATE_NOT_SUPPORTED:
@@ -393,7 +390,7 @@
setDispatchAvailableState(false);
}
- boolean enabled = able && enabledAvailable.first;
+ boolean enabled = mFeatureEnabled && able && enabledAvailable.first;
if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:"
+ currentDevice);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 11cca66..2a16872 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -298,7 +298,7 @@
return -1;
}
- if (promptInfo.containsTestConfigurations()) {
+ if (promptInfo.requiresTestOrInternalPermission()) {
if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC)
!= PackageManager.PERMISSION_GRANTED) {
checkInternalPermission();
@@ -306,10 +306,10 @@
}
// Only allow internal clients to enable non-public options.
- if (promptInfo.containsPrivateApiConfigurations()) {
+ if (promptInfo.requiresInternalPermission()) {
checkInternalPermission();
}
- if (promptInfo.containsAdvancedApiConfigurations()) {
+ if (promptInfo.requiresAdvancedPermission()) {
checkBiometricAdvancedPermission();
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d9c3ab8..30d12e6 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -1189,7 +1189,11 @@
update();
}
- void switchMode(@AutomaticBrightnessMode int mode) {
+ /**
+ * Responsible for switching the AutomaticBrightnessMode of the associated display. Also takes
+ * care of resetting the short term model wherever required
+ */
+ public void switchMode(@AutomaticBrightnessMode int mode) {
if (!mBrightnessMappingStrategyMap.contains(mode)) {
return;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 70a1014..0fcdf19 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -84,6 +84,7 @@
import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.config.HysteresisLevels;
@@ -797,7 +798,7 @@
return;
}
mDisplayStateController.overrideDozeScreenState(displayState, reason);
- sendUpdatePowerState();
+ updatePowerState();
}, mClock.uptimeMillis());
}
@@ -1333,12 +1334,6 @@
mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
- // Switch to doze auto-brightness mode if needed
- if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode()) {
- mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
- ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
- }
DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
.updateBrightness(mPowerRequest, state);
@@ -1372,6 +1367,13 @@
final boolean wasShortTermModelActive =
mAutomaticBrightnessStrategy.isShortTermModelActive();
if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+ // Switch to doze auto-brightness mode if needed
+ if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+ && !mAutomaticBrightnessController.isInIdleMode()) {
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+ }
+
mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
@@ -1440,45 +1442,52 @@
brightnessState = clampScreenBrightness(brightnessState);
}
- // If there's an offload session, we need to set the initial doze brightness before
- // the offload session starts controlling the brightness.
- // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
- // will be selected again, meaning that no new brightness will be sent to the hardware and
- // the display will stay at the brightness level set by the offload session.
- if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
- && Display.isDozeState(state) && mDisplayOffloadSession != null) {
- if (mAutomaticBrightnessController != null
- && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- // Use the auto-brightness curve and the last observed lux
- rawBrightnessState = mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastUsedLux(
- mTempBrightnessEvent);
- } else {
- rawBrightnessState = getDozeBrightnessForOffload();
- mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
- | BrightnessEvent.FLAG_DOZE_SCALE);
- }
-
- if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
- brightnessState = clampScreenBrightness(rawBrightnessState);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
-
+ if (Display.isDozeState(state)) {
+ // If there's an offload session, we need to set the initial doze brightness before
+ // the offload session starts controlling the brightness.
+ // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
+ // will be selected again, meaning that no new brightness will be sent to the hardware
+ // and the display will stay at the brightness level set by the offload session.
+ if ((Float.isNaN(brightnessState)
+ || displayBrightnessState.getDisplayBrightnessStrategyName()
+ .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
+ && mFlags.isDisplayOffloadEnabled()
+ && mDisplayOffloadSession != null) {
if (mAutomaticBrightnessController != null
&& mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- // Keep the brightness in the setting so that we can use it after the screen
- // turns on, until a lux sample becomes available. We don't do this when
- // auto-brightness is disabled - in that situation we still want to use
- // the last brightness from when the screen was on.
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ // Use the auto-brightness curve and the last observed lux
+ rawBrightnessState = mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastUsedLux(
+ mTempBrightnessEvent);
+ } else {
+ rawBrightnessState = getDozeBrightnessForOffload();
+ mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+ | BrightnessEvent.FLAG_DOZE_SCALE);
+ }
+
+ if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
+ brightnessState = clampScreenBrightness(rawBrightnessState);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+
+ if (mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ // Keep the brightness in the setting so that we can use it after the screen
+ // turns on, until a lux sample becomes available. We don't do this when
+ // auto-brightness is disabled - in that situation we still want to use
+ // the last brightness from when the screen was on.
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ }
}
}
- }
- // Use default brightness when dozing unless overridden.
- if (Float.isNaN(brightnessState) && Display.isDozeState(state)) {
- rawBrightnessState = mScreenBrightnessDozeConfig;
- brightnessState = clampScreenBrightness(rawBrightnessState);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
+ // Use default brightness when dozing unless overridden.
+ if (Float.isNaN(brightnessState)
+ || displayBrightnessState.getDisplayBrightnessStrategyName()
+ .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) {
+ rawBrightnessState = mScreenBrightnessDozeConfig;
+ brightnessState = clampScreenBrightness(rawBrightnessState);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
+ }
}
if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
@@ -1502,7 +1511,7 @@
}
// Apply manual brightness.
- if (Float.isNaN(brightnessState)) {
+ if (Float.isNaN(brightnessState) && !mFlags.isRefactorDisplayPowerControllerEnabled()) {
rawBrightnessState = currentBrightnessSetting;
brightnessState = clampScreenBrightness(rawBrightnessState);
if (brightnessState != currentBrightnessSetting) {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 22a21a6..feec4e6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -32,6 +32,7 @@
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
@@ -85,6 +86,9 @@
@Nullable
private final AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
+ @Nullable
+ private final FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+
// A collective representation of all the strategies that the selector is aware of. This is
// non null, but the strategies this is tracking can be null
@NonNull
@@ -118,7 +122,8 @@
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
mAutomaticBrightnessStrategy1 =
(!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
- : injector.getAutomaticBrightnessStrategy1(context, displayId);
+ : injector.getAutomaticBrightnessStrategy1(context, displayId,
+ mDisplayManagerFlags);
mAutomaticBrightnessStrategy2 =
(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
: injector.getAutomaticBrightnessStrategy2(context, displayId);
@@ -134,11 +139,14 @@
} else {
mOffloadBrightnessStrategy = null;
}
+ mFallbackBrightnessStrategy = (mDisplayManagerFlags
+ .isRefactorDisplayPowerControllerEnabled())
+ ? injector.getFallbackBrightnessStrategy() : null;
mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy,
- mAutoBrightnessFallbackStrategy};
+ mAutoBrightnessFallbackStrategy, mFallbackBrightnessStrategy};
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -179,6 +187,12 @@
displayBrightnessStrategy = mOffloadBrightnessStrategy;
} else if (isAutoBrightnessFallbackStrategyValid()) {
displayBrightnessStrategy = mAutoBrightnessFallbackStrategy;
+ } else {
+ // This will become the ultimate fallback strategy once the flag has been fully rolled
+ // out
+ if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+ displayBrightnessStrategy = mFallbackBrightnessStrategy;
+ }
}
if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
@@ -330,8 +344,8 @@
}
AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
- int displayId) {
- return new AutomaticBrightnessStrategy(context, displayId);
+ int displayId, DisplayManagerFlags displayManagerFlags) {
+ return new AutomaticBrightnessStrategy(context, displayId, displayManagerFlags);
}
AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
@@ -347,5 +361,9 @@
AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
return new AutoBrightnessFallbackStrategy(/* injector= */ null);
}
+
+ FallbackBrightnessStrategy getFallbackBrightnessStrategy() {
+ return new FallbackBrightnessStrategy();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2305228..f809a49 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -17,6 +17,9 @@
import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
@@ -33,6 +36,7 @@
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.StrategyExecutionRequest;
import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -98,19 +102,24 @@
private Injector mInjector;
+ private DisplayManagerFlags mDisplayManagerFlags;
+
@VisibleForTesting
- AutomaticBrightnessStrategy(Context context, int displayId, Injector injector) {
+ AutomaticBrightnessStrategy(Context context, int displayId, Injector injector,
+ DisplayManagerFlags displayManagerFlags) {
super(context, displayId);
mContext = context;
mDisplayId = displayId;
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mDisplayManagerFlags = displayManagerFlags;
mInjector = (injector == null) ? new RealInjector() : injector;
}
- public AutomaticBrightnessStrategy(Context context, int displayId) {
- this(context, displayId, null);
+ public AutomaticBrightnessStrategy(Context context, int displayId,
+ DisplayManagerFlags displayManagerFlags) {
+ this(context, displayId, null, displayManagerFlags);
}
/**
@@ -120,6 +129,7 @@
public void setAutoBrightnessState(int targetDisplayState,
boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+ switchMode(targetDisplayState);
final boolean autoBrightnessEnabledInDoze =
allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
@@ -479,6 +489,16 @@
}
}
+
+ private void switchMode(int state) {
+ if (mDisplayManagerFlags.areAutoBrightnessModesEnabled()
+ && mAutomaticBrightnessController != null
+ && !mAutomaticBrightnessController.isInIdleMode()) {
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+ }
+ }
+
/**
* Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
* Temporary brightness adjustments happen when the user moves the brightness slider in the
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
index 504683a..7b2f2b9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
@@ -18,4 +18,5 @@
public class DisplayBrightnessStrategyConstants {
static final String INVALID_BRIGHTNESS_STRATEGY_NAME = "InvalidBrightnessStrategy";
+ public static final String FALLBACK_BRIGHTNESS_STRATEGY_NAME = "FallbackBrightnessStrategy";
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
new file mode 100644
index 0000000..3463649a
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+import android.annotation.NonNull;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the associated display when no other strategy qualifies for
+ * setting up the brightness state. This strategy is also being used for evaluating the
+ * display brightness state when we have a manually set brightness. This is a temporary state, and
+ * the logic for evaluating the manual brightness will be moved to a separate strategy
+ */
+public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ StrategyExecutionRequest strategyExecutionRequest) {
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_MANUAL);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness())
+ .setSdrBrightness(strategyExecutionRequest.getCurrentScreenBrightness())
+ .setBrightnessReason(brightnessReason)
+ .setDisplayBrightnessStrategyName(getName())
+ // The fallback brightness might change due to clamping. Make sure we tell the rest
+ // of the system by updating the setting
+ .setShouldUpdateScreenBrightnessSetting(true)
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME;
+ }
+
+ @Override
+ public int getReason() {
+ return BrightnessReason.REASON_MANUAL;
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+
+ }
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+
+ }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0bb93a9..3883604 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -78,6 +78,7 @@
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.accessibility.Flags;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -356,6 +357,11 @@
case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
onAccessibilityDaltonizerChanged();
break;
+ case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL:
+ if (Flags.enableColorCorrectionSaturation()) {
+ onAccessibilityDaltonizerChanged();
+ }
+ break;
case Secure.DISPLAY_WHITE_BALANCE_ENABLED:
updateDisplayWhiteBalanceStatus();
break;
@@ -398,6 +404,11 @@
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.REDUCE_BRIGHT_COLORS_LEVEL),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+ if (Flags.enableColorCorrectionSaturation()) {
+ cr.registerContentObserver(
+ Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL),
+ false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+ }
// Apply the accessibility settings first, since they override most other settings.
onAccessibilityInversionChanged();
@@ -597,21 +608,31 @@
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
+ var contentResolver = getContext().getContentResolver();
final int daltonizerMode = isAccessiblityDaltonizerEnabled()
- ? Secure.getIntForUser(getContext().getContentResolver(),
- Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
- AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
+ ? Secure.getIntForUser(contentResolver,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
: AccessibilityManager.DALTONIZER_DISABLED;
+ int saturation = NOT_SET;
+ if (Flags.enableColorCorrectionSaturation()) {
+ saturation = Secure.getIntForUser(
+ contentResolver,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ NOT_SET,
+ mCurrentUser);
+ }
+
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
// Monochromacy isn't supported by the native Daltonizer implementation; use grayscale.
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE,
MATRIX_GRAYSCALE);
- dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
+ dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED, saturation);
} else {
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null);
- dtm.setDaltonizerMode(daltonizerMode);
+ dtm.setDaltonizerMode(daltonizerMode, saturation);
}
}
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index 0dba9e1..a76c427 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -16,6 +16,7 @@
package com.android.server.display.color;
+import android.annotation.IntRange;
import android.app.ActivityTaskManager;
import android.hardware.display.ColorDisplayManager;
import android.opengl.Matrix;
@@ -111,9 +112,15 @@
/**
* Lock used for synchronize access to {@link #mDaltonizerMode}.
*/
- private final Object mDaltonizerModeLock = new Object();
+ @VisibleForTesting
+ final Object mDaltonizerModeLock = new Object();
+ @VisibleForTesting
@GuardedBy("mDaltonizerModeLock")
- private int mDaltonizerMode = -1;
+ int mDaltonizerMode = -1;
+
+ @VisibleForTesting
+ @GuardedBy("mDaltonizerModeLock")
+ int mDaltonizerLevel = -1;
private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER);
@@ -168,12 +175,15 @@
* various types of color blindness.
*
* @param mode the new Daltonization mode, or -1 to disable
+ * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when
+ * it is not set.
*/
- public void setDaltonizerMode(int mode) {
+ public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) {
synchronized (mDaltonizerModeLock) {
- if (mDaltonizerMode != mode) {
+ if (mDaltonizerMode != mode || mDaltonizerLevel != level) {
mDaltonizerMode = mode;
- applyDaltonizerMode(mode);
+ mDaltonizerLevel = level;
+ applyDaltonizerMode(mode, level);
}
}
}
@@ -223,10 +233,11 @@
/**
* Propagates the provided Daltonization mode to the SurfaceFlinger.
*/
- private static void applyDaltonizerMode(int mode) {
+ private static void applyDaltonizerMode(int mode, int level) {
final Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
data.writeInt(mode);
+ data.writeInt(level);
try {
sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/display/config/RefreshRateData.java b/services/core/java/com/android/server/display/config/RefreshRateData.java
index b186fd5..d7ed904 100644
--- a/services/core/java/com/android/server/display/config/RefreshRateData.java
+++ b/services/core/java/com/android/server/display/config/RefreshRateData.java
@@ -20,6 +20,10 @@
import android.content.res.Resources;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
/**
* RefreshRates config for display
@@ -58,12 +62,17 @@
*/
public final int defaultRefreshRateInHbmSunlight;
+ public final List<SupportedModeData> lowPowerSupportedModes;
+
+ @VisibleForTesting
public RefreshRateData(int defaultRefreshRate, int defaultPeakRefreshRate,
- int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight) {
+ int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight,
+ List<SupportedModeData> lowPowerSupportedModes) {
this.defaultRefreshRate = defaultRefreshRate;
this.defaultPeakRefreshRate = defaultPeakRefreshRate;
this.defaultRefreshRateInHbmHdr = defaultRefreshRateInHbmHdr;
this.defaultRefreshRateInHbmSunlight = defaultRefreshRateInHbmSunlight;
+ this.lowPowerSupportedModes = Collections.unmodifiableList(lowPowerSupportedModes);
}
@@ -71,9 +80,10 @@
public String toString() {
return "RefreshRateData {"
+ "defaultRefreshRate: " + defaultRefreshRate
- + "defaultPeakRefreshRate: " + defaultPeakRefreshRate
- + "defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr
- + "defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight
+ + ", defaultPeakRefreshRate: " + defaultPeakRefreshRate
+ + ", defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr
+ + ", defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight
+ + ", lowPowerSupportedModes=" + lowPowerSupportedModes
+ "} ";
}
@@ -90,8 +100,13 @@
int defaultRefreshRateInHbmSunlight = loadDefaultRefreshRateInHbmSunlight(
refreshRateConfigs, resources);
+ NonNegativeFloatToFloatMap modes =
+ refreshRateConfigs == null ? null : refreshRateConfigs.getLowPowerSupportedModes();
+ List<SupportedModeData> lowPowerSupportedModes = SupportedModeData.load(modes);
+
return new RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
- defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight);
+ defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+ lowPowerSupportedModes);
}
private static int loadDefaultRefreshRate(
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 6ad13c3..8bfc4a3 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -24,7 +24,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.feature.DisplayManagerFlags;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -42,7 +41,7 @@
public final String name;
public final float minRefreshRate;
public final float maxRefreshRate;
- public final List<SupportedMode> supportedModes;
+ public final List<SupportedModeData> supportedModes;
@VisibleForTesting
public SensorData() {
@@ -61,7 +60,7 @@
@VisibleForTesting
public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
- List<SupportedMode> supportedModes) {
+ List<SupportedModeData> supportedModes) {
this.type = type;
this.name = name;
this.minRefreshRate = minRefreshRate;
@@ -214,26 +213,11 @@
minRefreshRate = rr.getMinimum().floatValue();
maxRefreshRate = rr.getMaximum().floatValue();
}
- ArrayList<SupportedMode> supportedModes = new ArrayList<>();
- NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes();
- if (configSupportedModes != null) {
- for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) {
- supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(),
- supportedMode.getSecond().floatValue()));
- }
- }
+ List<SupportedModeData> supportedModes = SupportedModeData.load(
+ sensorDetails.getSupportedModes());
return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate,
maxRefreshRate, supportedModes);
}
- public static class SupportedMode {
- public final float refreshRate;
- public final float vsyncRate;
-
- public SupportedMode(float refreshRate, float vsyncRate) {
- this.refreshRate = refreshRate;
- this.vsyncRate = vsyncRate;
- }
- }
}
diff --git a/services/core/java/com/android/server/display/config/SupportedModeData.java b/services/core/java/com/android/server/display/config/SupportedModeData.java
new file mode 100644
index 0000000..3c82884
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/SupportedModeData.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Supported display mode data. Display mode is uniquely identified by refreshRate-vsync pair
+ */
+public class SupportedModeData {
+ public final float refreshRate;
+ public final float vsyncRate;
+
+ public SupportedModeData(float refreshRate, float vsyncRate) {
+ this.refreshRate = refreshRate;
+ this.vsyncRate = vsyncRate;
+ }
+
+ @Override
+ public String toString() {
+ return "SupportedModeData{"
+ + "refreshRate= " + refreshRate
+ + ", vsyncRate= " + vsyncRate
+ + '}';
+ }
+
+ static List<SupportedModeData> load(@Nullable NonNegativeFloatToFloatMap configMap) {
+ ArrayList<SupportedModeData> supportedModes = new ArrayList<>();
+ if (configMap != null) {
+ for (NonNegativeFloatToFloatPoint supportedMode : configMap.getPoint()) {
+ supportedModes.add(new SupportedModeData(supportedMode.getFirst().floatValue(),
+ supportedMode.getSecond().floatValue()));
+ }
+ }
+ return supportedModes;
+ }
+}
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 91bd80e..846ee23 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -78,6 +78,7 @@
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.RefreshRateData;
+import com.android.server.display.config.SupportedModeData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.utils.AmbientFilter;
@@ -451,15 +452,6 @@
return config != null && config.isVrrSupportEnabled();
}
- private boolean isVrrSupportedByAnyDisplayLocked() {
- for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
- if (mDisplayDeviceConfigByDisplay.valueAt(i).isVrrSupportEnabled()) {
- return true;
- }
- }
- return false;
- }
-
/**
* Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges.
*/
@@ -939,18 +931,44 @@
private final Uri mMatchContentFrameRateSetting =
Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE);
- private final boolean mVsynLowPowerVoteEnabled;
+ private final boolean mVsyncLowPowerVoteEnabled;
private final boolean mPeakRefreshRatePhysicalLimitEnabled;
private final Context mContext;
+ private final Handler mHandler;
private float mDefaultPeakRefreshRate;
private float mDefaultRefreshRate;
+ private boolean mIsLowPower = false;
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ updateLowPowerModeAllowedModesLocked();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_LOW_POWER_MODE_MODES,
+ null);
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ synchronized (mLock) {
+ updateLowPowerModeAllowedModesLocked();
+ }
+ }
+ };
SettingsObserver(@NonNull Context context, @NonNull Handler handler,
DisplayManagerFlags flags) {
super(handler);
mContext = context;
- mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled();
+ mHandler = handler;
+ mVsyncLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled();
mPeakRefreshRatePhysicalLimitEnabled = flags.isPeakRefreshRatePhysicalLimitEnabled();
// We don't want to load from the DeviceConfig while constructing since this leads to
// a spike in the latency of DisplayManagerService startup. This happens because
@@ -983,6 +1001,7 @@
UserHandle.USER_SYSTEM);
cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
this);
+ mInjector.registerDisplayListener(mDisplayListener, mHandler);
float deviceConfigDefaultPeakRefresh =
mConfigParameterProvider.getPeakRefreshRateDefault();
@@ -995,6 +1014,7 @@
updateLowPowerModeSettingLocked();
updateModeSwitchingTypeSettingLocked();
}
+
}
public void setDefaultRefreshRate(float refreshRate) {
@@ -1061,23 +1081,36 @@
}
private void updateLowPowerModeSettingLocked() {
- boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
+ mIsLowPower = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
final Vote vote;
- if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) {
- vote = Vote.forSupportedRefreshRates(List.of(
- new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
- /* vsyncRate= */ 240f),
- new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
- /* vsyncRate= */ 60f)
- ));
- } else if (inLowPowerMode) {
+ if (mIsLowPower) {
vote = Vote.forRenderFrameRates(0f, 60f);
} else {
vote = null;
}
- mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote);
- mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
+ mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, vote);
+ mBrightnessObserver.onLowPowerModeEnabledLocked(mIsLowPower);
+ updateLowPowerModeAllowedModesLocked();
+ }
+
+ private void updateLowPowerModeAllowedModesLocked() {
+ if (!mVsyncLowPowerVoteEnabled) {
+ return;
+ }
+ if (mIsLowPower) {
+ for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
+ DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.valueAt(i);
+ List<SupportedModeData> supportedModes = config
+ .getRefreshRateData().lowPowerSupportedModes;
+ mVotesStorage.updateVote(
+ mDisplayDeviceConfigByDisplay.keyAt(i),
+ Vote.PRIORITY_LOW_POWER_MODE_MODES,
+ Vote.forSupportedRefreshRates(supportedModes));
+ }
+ } else {
+ mVotesStorage.removeAllVotesForPriority(Vote.PRIORITY_LOW_POWER_MODE_MODES);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index ddb334e..8167c1f 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
+import com.android.server.display.config.SupportedModeData;
+
+import java.util.ArrayList;
import java.util.List;
interface Vote {
@@ -102,9 +105,15 @@
// For internal application to limit display modes to specific ids
int PRIORITY_SYSTEM_REQUESTED_MODES = 14;
- // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
+ // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
// Settings.Global.LOW_POWER_MODE is on.
- int PRIORITY_LOW_POWER_MODE = 15;
+ // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
+ // higher priority votes), render rate limit can still apply
+ int PRIORITY_LOW_POWER_MODE_MODES = 14;
+
+ // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
+ // Settings.Global.LOW_POWER_MODE is on.
+ int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 15;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
@@ -177,22 +186,26 @@
return new BaseModeRefreshRateVote(baseModeRefreshRate);
}
- static Vote forSupportedRefreshRates(
- List<SupportedRefreshRatesVote.RefreshRates> refreshRates) {
- return new SupportedRefreshRatesVote(refreshRates);
+ static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) {
+ if (supportedModes.isEmpty()) {
+ return null;
+ }
+ List<SupportedRefreshRatesVote.RefreshRates> rates = new ArrayList<>();
+ for (SupportedModeData data : supportedModes) {
+ rates.add(new SupportedRefreshRatesVote.RefreshRates(data.refreshRate, data.vsyncRate));
+ }
+ return new SupportedRefreshRatesVote(rates);
}
static Vote forSupportedModes(List<Integer> modeIds) {
return new SupportedModesVote(modeIds);
}
-
-
static Vote forSupportedRefreshRatesAndDisableSwitching(
List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) {
return new CombinedVote(
List.of(forDisableRefreshRateSwitching(),
- forSupportedRefreshRates(supportedRefreshRates)));
+ new SupportedRefreshRatesVote(supportedRefreshRates)));
}
static String priorityToString(int priority) {
@@ -213,8 +226,10 @@
return "PRIORITY_HIGH_BRIGHTNESS_MODE";
case PRIORITY_PROXIMITY:
return "PRIORITY_PROXIMITY";
- case PRIORITY_LOW_POWER_MODE:
- return "PRIORITY_LOW_POWER_MODE";
+ case PRIORITY_LOW_POWER_MODE_MODES:
+ return "PRIORITY_LOW_POWER_MODE_MODES";
+ case PRIORITY_LOW_POWER_MODE_RENDER_RATE:
+ return "PRIORITY_LOW_POWER_MODE_RENDER_RATE";
case PRIORITY_SKIN_TEMPERATURE:
return "PRIORITY_SKIN_TEMPERATURE";
case PRIORITY_UDFPS:
@@ -227,6 +242,8 @@
return "PRIORITY_LIMIT_MODE";
case PRIORITY_SYNCHRONIZED_REFRESH_RATE:
return "PRIORITY_SYNCHRONIZED_REFRESH_RATE";
+ case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+ return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 816242d..c6aef7f 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -249,14 +249,14 @@
mCurrentDream.mAppTask = appTask;
}
- void setDreamHasFocus(boolean hasFocus) {
+ void setDreamIsObscured(boolean isObscured) {
if (mCurrentDream != null) {
- mCurrentDream.mDreamHasFocus = hasFocus;
+ mCurrentDream.mDreamIsObscured = isObscured;
}
}
- boolean dreamHasFocus() {
- return mCurrentDream != null && mCurrentDream.mDreamHasFocus;
+ boolean dreamIsFrontmost() {
+ return mCurrentDream != null && mCurrentDream.dreamIsFrontmost();
}
/**
@@ -451,7 +451,7 @@
private String mStopReason;
private long mDreamStartTime;
public boolean mWakingGently;
- public boolean mDreamHasFocus;
+ private boolean mDreamIsObscured;
private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
@@ -549,5 +549,9 @@
mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
}
}
+
+ boolean dreamIsFrontmost() {
+ return !mDreamIsObscured;
+ }
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 2def5ae..18a9986 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,7 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.service.dreams.Flags.dreamTracksFocus;
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -428,7 +428,7 @@
// Can't start dreaming if we are already dreaming and the dream has focus. If we are
// dreaming but the dream does not have focus, then the dream can be brought to the
// front so it does have focus.
- if (isScreenOn && isDreamingInternal() && dreamHasFocus()) {
+ if (isScreenOn && isDreamingInternal() && dreamIsFrontmost()) {
return false;
}
@@ -463,9 +463,10 @@
}
}
- private boolean dreamHasFocus() {
- // Dreams always had focus before they were able to track it.
- return !dreamTracksFocus() || mController.dreamHasFocus();
+ private boolean dreamIsFrontmost() {
+ // Dreams were always considered frontmost before they began tracking whether they are
+ // obscured.
+ return !dreamHandlesBeingObscured() || mController.dreamIsFrontmost();
}
protected void requestStartDreamFromShell() {
@@ -473,7 +474,7 @@
}
private void requestDreamInternal() {
- if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) {
+ if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
return;
}
@@ -1159,10 +1160,16 @@
}
@Override
- public void onDreamFocusChanged(boolean hasFocus) {
+ public void setDreamIsObscured(boolean isObscured) {
+ if (!dreamHandlesBeingObscured()) {
+ return;
+ }
+
+ checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
final long ident = Binder.clearCallingIdentity();
try {
- mController.setDreamHasFocus(hasFocus);
+ mHandler.post(() -> mController.setDreamIsObscured(isObscured));
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d21fc85..5db17bb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -29,7 +29,6 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.sysprop.HdmiProperties;
import android.util.Slog;
@@ -278,8 +277,7 @@
void dismissUiOnActiveSourceStatusRecovered() {
assertRunOnServiceThread();
Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
- mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- HdmiControlService.PERMISSION);
+ mService.sendBroadcastAsUser(intent);
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 46061a5..275c930 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -206,6 +206,10 @@
launchDeviceDiscovery();
startQueuedActions();
if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+ if (hasAction(RequestActiveSourceAction.class)) {
+ Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
+ removeAction(RequestActiveSourceAction.class);
+ }
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
@@ -1308,6 +1312,8 @@
mService.sendCecCommand(
HdmiCecMessageBuilder.buildActiveSource(
getDeviceInfo().getLogicalAddress(), activePath));
+ updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath,
+ "HdmiCecLocalDeviceTv#launchRoutingControl()");
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index d2d0279..05c4aa6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -40,6 +40,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -1645,6 +1646,13 @@
case Constants.MESSAGE_ROUTING_CHANGE:
case Constants.MESSAGE_SET_STREAM_PATH:
case Constants.MESSAGE_TEXT_VIEW_ON:
+ // RequestActiveSourceAction is started after the TV finished logical address
+ // allocation. This action is used by the TV to get the active source from the CEC
+ // network. If the TV sent a source changing CEC message, this action does not have
+ // to continue anymore.
+ if (isTvDevice()) {
+ tv().removeAction(RequestActiveSourceAction.class);
+ }
sendCecCommandWithRetries(command, callback);
break;
default:
@@ -4392,8 +4400,7 @@
assertRunOnServiceThread();
Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
- getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- HdmiControlService.PERMISSION);
+ sendBroadcastAsUser(intent);
}
@ServiceThreadOnly
@@ -4402,8 +4409,17 @@
Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
- getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- HdmiControlService.PERMISSION);
+ sendBroadcastAsUser(intent);
+ }
+
+ // This method is used such that we can override it inside unit tests to avoid a
+ // SecurityException.
+ @ServiceThreadOnly
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ assertRunOnServiceThread();
+ getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index d250416..539a00d 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -21,13 +21,20 @@
import android.util.Slog;
/**
- * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
+ * panels.
+ * This action has a delay before sending <Request Active Source>. This is because it should wait
+ * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
+ * received or the TV switched to another input.
*/
public class RequestActiveSourceAction extends HdmiCecFeatureAction {
private static final String TAG = "RequestActiveSourceAction";
+ // State to wait for the LauncherX to call the CEC API.
+ private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1;
+
// State to wait for the <Active Source> message.
- private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1;
+ private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2;
// Number of retries <Request Active Source> is sent if no device answers this message.
private static final int MAX_SEND_RETRY_COUNT = 1;
@@ -43,10 +50,12 @@
boolean start() {
Slog.v(TAG, "RequestActiveSourceAction started.");
- sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
- mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ // We wait for default timeout to allow the message triggered by the LauncherX API call to
+ // be sent by the TV and another default timeout in case the message has to be answered
+ // (e.g. TV sent a <Set Stream Path> or <Routing Change>).
+ addTimer(mState, HdmiConfig.TIMEOUT_MS * 2);
return true;
}
@@ -65,13 +74,23 @@
if (mState != state) {
return;
}
- if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) {
- if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+
+ switch (mState) {
+ case STATE_WAIT_FOR_LAUNCHERX_API_CALL:
+ mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- } else {
- finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
- }
+ return;
+ case STATE_WAIT_FOR_ACTIVE_SOURCE:
+ if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+ sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+ }
+ return;
+ default:
+ return;
}
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index b47631c3..d32a5ed 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -218,4 +218,13 @@
* display, external peripherals, fingerprint sensor, etc.
*/
public abstract void notifyUserActivity();
+
+ /**
+ * Get the device ID of the {@link InputDevice} that used most recently.
+ *
+ * @return the last used input device ID, or
+ * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used
+ * since boot.
+ */
+ public abstract int getLastUsedInputDeviceId();
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8317991..8e85b81 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2671,24 +2671,6 @@
return null;
}
- private static class PointerDisplayIdChangedArgs {
- final int mPointerDisplayId;
- final float mXPosition;
- final float mYPosition;
- PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
- mPointerDisplayId = pointerDisplayId;
- mXPosition = xPosition;
- mYPosition = yPosition;
- }
- }
-
- // Native callback.
- @SuppressWarnings("unused")
- @VisibleForTesting
- void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
- // TODO(b/311416205): Remove.
- }
-
@Override
@EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
public void registerStickyModifierStateListener(
@@ -3204,6 +3186,11 @@
public void setStylusButtonMotionEventsEnabled(boolean enabled) {
mNative.setStylusButtonMotionEventsEnabled(enabled);
}
+
+ @Override
+ public int getLastUsedInputDeviceId() {
+ return mNative.getLastUsedInputDeviceId();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index f742360..0208a32 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -271,6 +271,15 @@
void setInputMethodConnectionIsActive(boolean isActive);
+ /**
+ * Get the device ID of the InputDevice that used most recently.
+ *
+ * @return the last used input device ID, or
+ * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used
+ * since boot.
+ */
+ int getLastUsedInputDeviceId();
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -544,5 +553,8 @@
@Override
public native void setInputMethodConnectionIsActive(boolean isActive);
+
+ @Override
+ public native int getLastUsedInputDeviceId();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index 00bc751..ad98b4a 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
/**
@@ -49,12 +50,12 @@
private static final class CreateInlineSuggestionsRequest {
@NonNull final InlineSuggestionsRequestInfo mRequestInfo;
- @NonNull final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull final InlineSuggestionsRequestCallback mCallback;
@NonNull final String mPackageName;
CreateInlineSuggestionsRequest(
@NonNull InlineSuggestionsRequestInfo requestInfo,
- @NonNull IInlineSuggestionsRequestCallback callback,
+ @NonNull InlineSuggestionsRequestCallback callback,
@NonNull String packageName) {
mRequestInfo = requestInfo;
mCallback = callback;
@@ -78,7 +79,7 @@
*/
@GuardedBy("ImfLock.class")
@Nullable
- private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+ private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
AutofillSuggestionsController(@NonNull InputMethodManagerService service) {
mService = service;
@@ -97,33 +98,30 @@
@GuardedBy("ImfLock.class")
void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
+ InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback callback,
boolean touchExplorationEnabled) {
clearPendingInlineSuggestionsRequest();
mInlineSuggestionsRequestCallback = callback;
final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked(
mService.getSelectedMethodIdLocked());
- try {
- if (userId == mService.getCurrentImeUserIdLocked()
- && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
- mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
- requestInfo, callback, imi.getPackageName());
- if (mService.getCurMethodLocked() != null) {
- // In the normal case when the IME is connected, we can make the request here.
- performOnCreateInlineSuggestionsRequest();
- } else {
- // Otherwise, the next time the IME connection is established,
- // InputMethodBindingController.mMainConnection#onServiceConnected() will call
- // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
- if (DEBUG) {
- Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
- }
- }
+
+ if (userId == mService.getCurrentImeUserIdLocked()
+ && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
+ mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
+ requestInfo, callback, imi.getPackageName());
+ if (mService.getCurMethodLocked() != null) {
+ // In the normal case when the IME is connected, we can make the request here.
+ performOnCreateInlineSuggestionsRequest();
} else {
- callback.onInlineSuggestionsUnsupported();
+ // Otherwise, the next time the IME connection is established,
+ // InputMethodBindingController.mMainConnection#onServiceConnected() will call
+ // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
+ if (DEBUG) {
+ Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
+ }
}
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+ } else {
+ callback.onInlineSuggestionsUnsupported();
}
}
@@ -166,11 +164,7 @@
@GuardedBy("ImfLock.class")
void invalidateAutofillSession() {
if (mInlineSuggestionsRequestCallback != null) {
- try {
- mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated();
- } catch (RemoteException e) {
- Slog.e(TAG, "Cannot invalidate autofill session.", e);
- }
+ mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated();
}
}
@@ -180,13 +174,13 @@
*/
private final class InlineSuggestionsRequestCallbackDecorator
extends IInlineSuggestionsRequestCallback.Stub {
- @NonNull private final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull private final InlineSuggestionsRequestCallback mCallback;
@NonNull private final String mImePackageName;
private final int mImeDisplayId;
@NonNull private final IBinder mImeToken;
InlineSuggestionsRequestCallbackDecorator(
- @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
+ @NonNull InlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
int displayId, @NonNull IBinder imeToken) {
mCallback = callback;
mImePackageName = imePackageName;
@@ -195,7 +189,7 @@
}
@Override
- public void onInlineSuggestionsUnsupported() throws RemoteException {
+ public void onInlineSuggestionsUnsupported() {
mCallback.onInlineSuggestionsUnsupported();
}
@@ -220,32 +214,32 @@
}
@Override
- public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+ public void onInputMethodStartInput(AutofillId imeFieldId) {
mCallback.onInputMethodStartInput(imeFieldId);
}
@Override
- public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+ public void onInputMethodShowInputRequested(boolean requestResult) {
mCallback.onInputMethodShowInputRequested(requestResult);
}
@Override
- public void onInputMethodStartInputView() throws RemoteException {
+ public void onInputMethodStartInputView() {
mCallback.onInputMethodStartInputView();
}
@Override
- public void onInputMethodFinishInputView() throws RemoteException {
+ public void onInputMethodFinishInputView() {
mCallback.onInputMethodFinishInputView();
}
@Override
- public void onInputMethodFinishInput() throws RemoteException {
+ public void onInputMethodFinishInput() {
mCallback.onInputMethodFinishInput();
}
@Override
- public void onInlineSuggestionsSessionInvalidated() throws RemoteException {
+ public void onInlineSuggestionsSessionInvalidated() {
mCallback.onInlineSuggestionsSessionInvalidated();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
index 4c20c3b..f78ea84 100644
--- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -21,7 +21,9 @@
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.IBinder;
+import android.os.UserHandle;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
import android.view.WindowManager;
@@ -36,6 +38,9 @@
*/
final class ImeBindingState {
+ @UserIdInt
+ final int mUserId;
+
/**
* The last window token that we confirmed to be focused. This is always updated upon
* reports from the input method client. If the window state is already changed before the
@@ -90,6 +95,7 @@
static ImeBindingState newEmptyState() {
return new ImeBindingState(
+ /*userId=*/ UserHandle.USER_NULL,
/*focusedWindow=*/ null,
/*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
/*focusedWindowClient=*/ null,
@@ -97,10 +103,12 @@
);
}
- ImeBindingState(@Nullable IBinder focusedWindow,
+ ImeBindingState(@UserIdInt int userId,
+ @Nullable IBinder focusedWindow,
@SoftInputModeFlags int focusedWindowSoftInputMode,
@Nullable ClientState focusedWindowClient,
@Nullable EditorInfo focusedWindowEditorInfo) {
+ mUserId = userId;
mFocusedWindow = focusedWindow;
mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
mFocusedWindowClient = focusedWindowClient;
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 1c14fc1..fff0e6e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -133,6 +133,13 @@
}
}
+ @Override
+ public void onDispatched(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
+ }
+
/**
* Updates the IME request tracking token with new information available in IMMS.
*
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b709174..e862c7e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -443,7 +443,16 @@
mCurId = info.getId();
mLastBindTime = SystemClock.uptimeMillis();
- addFreshWindowToken();
+ final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
+ mCurToken = new Binder();
+ mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
+ if (DEBUG) {
+ Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+ + displayIdToShowIme);
+ }
+ mWindowManagerInternal.addWindowToken(mCurToken,
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+ displayIdToShowIme, null /* options */);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, null, mCurId, mCurSeq, false);
@@ -471,22 +480,6 @@
}
@GuardedBy("ImfLock.class")
- private void addFreshWindowToken() {
- int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
- mCurToken = new Binder();
-
- mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
-
- if (DEBUG) {
- Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
- + displayIdToShowIme);
- }
- mWindowManagerInternal.addWindowToken(mCurToken,
- WindowManager.LayoutParams.TYPE_INPUT_METHOD,
- displayIdToShowIme, null /* options */);
- }
-
- @GuardedBy("ImfLock.class")
private void unbindMainConnection() {
mContext.unbindService(mMainConnection);
mHasMainConnection = false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 6339686..458c359 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Parcel;
import android.text.TextUtils;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
@@ -323,4 +324,24 @@
return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry,
requiredSubtypeMode);
}
+
+ /**
+ * Marshals the given {@link InputMethodInfo} into a byte array.
+ *
+ * @param imi {@link InputMethodInfo} to be marshalled
+ * @return a byte array where the given {@link InputMethodInfo} is marshalled
+ */
+ @NonNull
+ static byte[] marshal(@NonNull InputMethodInfo imi) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.writeTypedObject(imi, 0);
+ return parcel.marshall();
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index e8543f2..dace67f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -25,7 +25,7 @@
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
@@ -86,11 +86,11 @@
*
* @param userId the user ID to be queried
* @param requestInfo information needed to create an {@link InlineSuggestionsRequest}.
- * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request
+ * @param cb {@link InlineSuggestionsRequestCallback} used to pass back the request
* object
*/
public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb);
+ InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb);
/**
* Force switch to the enabled input method by {@code imeId} for current user. If the input
@@ -263,7 +263,7 @@
@Override
public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo,
- IInlineSuggestionsRequestCallback cb) {
+ InlineSuggestionsRequestCallback cb) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 848f74e..46c5772 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -147,7 +147,6 @@
import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IInputMethodClient;
@@ -157,6 +156,7 @@
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -262,6 +262,7 @@
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
private static final String HANDLER_THREAD_NAME = "android.imms";
+ private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
/**
* When set, {@link #startInputUncheckedLocked} will return
@@ -285,6 +286,9 @@
final Resources mRes;
private final Handler mHandler;
+ @NonNull
+ private final Handler mPackageMonitorHandler;
+
@MultiUserUnawareField
@UserIdInt
@GuardedBy("ImfLock.class")
@@ -485,16 +489,6 @@
return userData.mBindingController.getSelectedMethodId();
}
- /**
- * The current binding sequence number, incremented every time there is
- * a new bind performed.
- */
- @GuardedBy("ImfLock.class")
- private int getSequenceNumberLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getSequenceNumber();
- }
-
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -543,21 +537,6 @@
EditorInfo mCurEditorInfo;
/**
- * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
- * connected to or in the process of connecting to.
- *
- * <p>This can be {@code null} when no input method is connected.</p>
- *
- * @see #getSelectedMethodIdLocked()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private String getCurIdLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurId();
- }
-
- /**
* The current subtype of the current input method.
*/
@MultiUserUnawareField
@@ -587,16 +566,6 @@
boolean mInFullscreenMode;
/**
- * The Intent used to connect to the current input method.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private Intent getCurIntentLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurIntent();
- }
-
- /**
* The token we have made for the currently active input method, to
* identify it in the future.
*/
@@ -642,15 +611,6 @@
}
/**
- * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}.
- */
- @GuardedBy("ImfLock.class")
- private int getCurMethodUidLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurMethodUid();
- }
-
- /**
* Have we called mCurMethod.bindInput()?
*/
@MultiUserUnawareField
@@ -1068,8 +1028,16 @@
}
private void onFinishPackageChangesInternal() {
+ final int userId = getChangingUserId();
+
+ // Instantiating InputMethodInfo requires disk I/O.
+ // Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
+ final var newMethodMapWithoutAdditionalSubtypes =
+ queryInputMethodServicesInternal(mContext, userId,
+ AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO)
+ .getMethodMap();
+
synchronized (ImfLock.class) {
- final int userId = getChangingUserId();
final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -1121,9 +1089,10 @@
&& !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
-
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
- userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ final var newMethodMap = newMethodMapWithoutAdditionalSubtypes
+ .applyAdditionalSubtypes(newAdditionalSubtypeMap);
+ final InputMethodSettings newSettings =
+ InputMethodSettings.create(newMethodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
if (!isCurrentUser) {
return;
@@ -1323,13 +1292,14 @@
}
public InputMethodManagerService(Context context) {
- this(context, null, null);
+ this(context, null, null, null);
}
@VisibleForTesting
InputMethodManagerService(
Context context,
@Nullable ServiceThread serviceThreadForTesting,
+ @Nullable ServiceThread packageMonitorThreadForTesting,
@Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
synchronized (ImfLock.class) {
mContext = context;
@@ -1347,6 +1317,17 @@
true /* allowIo */);
thread.start();
mHandler = Handler.createAsync(thread.getLooper(), this);
+ {
+ final ServiceThread packageMonitorThread =
+ packageMonitorThreadForTesting != null
+ ? packageMonitorThreadForTesting
+ : new ServiceThread(
+ PACKAGE_MONITOR_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
+ packageMonitorThread.start();
+ mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper());
+ }
SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
@@ -1522,7 +1503,8 @@
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.unbindCurrentMethod();
+ final var bindingController = userData.mBindingController;
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
@@ -1620,7 +1602,7 @@
}
}, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
- mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
+ mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler);
mSettingsObserver.registerContentObserverLocked(currentUserId);
final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1741,9 +1723,10 @@
// Check if selected IME of current user supports handwriting.
if (userId == mCurrentUserId) {
final var userData = mUserDataRepository.getOrCreate(userId);
- return userData.mBindingController.supportsStylusHandwriting()
+ final var bindingController = userData.mBindingController;
+ return bindingController.supportsStylusHandwriting()
&& (!connectionless
- || userData.mBindingController.supportsConnectionlessStylusHandwriting());
+ || bindingController.supportsConnectionlessStylusHandwriting());
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
@@ -1915,7 +1898,6 @@
return mClientController.getClient(client.asBinder());
}
- // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -1935,7 +1917,14 @@
// all accessibility too. That means, when input method get disconnected (including
// switching ime), we also unbind accessibility
mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
- mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason);
+
+ // TODO(b/325515685): make binding controller user independent. Before this change, the
+ // following dependencies also need to be user independent: mCurClient, mBoundToMethod,
+ // getCurMethodLocked(), and mMenuController.
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
+ mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
+ unbindClientReason);
mCurClient.mSessionRequested = false;
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
@@ -2013,12 +2002,15 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
- mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
- UserHandle.getUserId(mCurClient.mUid),
+ getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
+ restarting, UserHandle.getUserId(mCurClient.mUid),
mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
- mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+ mImeBindingState.mFocusedWindowSoftInputMode,
+ bindingController.getSequenceNumber());
mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
@@ -2029,8 +2021,8 @@
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
if (mCurrentUserId == UserHandle.getUserId(
mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
- null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
+ mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */,
+ UserHandle.getAppId(bindingController.getCurMethodUid()),
mCurClient.mUid, true /* direct */);
}
@@ -2051,21 +2043,20 @@
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
- String curId = getCurIdLocked();
+ final var curId = bindingController.getCurId();
final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
.getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.mSession, accessibilityInputMethodSessions,
(session.mChannel != null ? session.mChannel.dup() : null),
- curId, getSequenceNumberLocked(), suppressesSpellChecker);
+ curId, bindingController.getSequenceNumber(), suppressesSpellChecker);
}
@GuardedBy("ImfLock.class")
@@ -2159,7 +2150,8 @@
final boolean connectionWasActive = mCurInputConnection != null;
// Bump up the sequence for this client and attach it.
- userData.mBindingController.advanceSequenceNumber();
+ final var bindingController = userData.mBindingController;
+ bindingController.advanceSequenceNumber();
mCurClient = cs;
mCurInputConnection = inputConnection;
@@ -2182,7 +2174,6 @@
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
- final var bindingController = userData.mBindingController;
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2200,7 +2191,7 @@
// display ID.
final String curId = bindingController.getCurId();
if (curId != null && curId.equals(bindingController.getSelectedMethodId())
- && mDisplayIdToShowIme == mCurTokenDisplayId) {
+ && mDisplayIdToShowIme == getCurTokenDisplayIdLocked()) {
if (cs.mCurSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2333,7 +2324,8 @@
@Nullable
private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
@NonNull ClientState cs) {
- if (userData.mBindingController.hasMainConnection()) {
+ final var bindingController = userData.mBindingController;
+ if (bindingController.hasMainConnection()) {
if (getCurMethodLocked() != null) {
// Return to client, and we will get back with it when
// we have had a session made for it.
@@ -2341,9 +2333,11 @@
requestClientSessionForAccessibilityLocked(cs);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+ null, null, null,
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(), false);
} else {
- final long lastBindTime = userData.mBindingController.getLastBindTime();
+ final long lastBindTime = bindingController.getLastBindTime();
long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
if (bindingDuration < TIME_TO_RECONNECT) {
// In this case we have connected to the service, but
@@ -2355,7 +2349,9 @@
// to see if we can get back in touch with the service.
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+ null, null, null,
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(), false);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -2402,7 +2398,7 @@
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + mCurTokenDisplayId);
+ + getCurTokenDisplayIdLocked());
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
getInputMethodNavButtonFlagsLocked());
@@ -2482,13 +2478,14 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.setSelectedMethodId(null);
+ final var bindingController =
+ mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+ bindingController.setSelectedMethodId(null);
// Callback before clean-up binding states.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset();
- userData.mBindingController.unbindCurrentMethod();
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(unbindClientReason);
}
@@ -2691,9 +2688,10 @@
}
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
+ final int tokenDisplayId = getCurTokenDisplayIdLocked();
final boolean hasNavigationBar = mWindowManagerInternal
- .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
- ? mCurTokenDisplayId : DEFAULT_DISPLAY);
+ .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
+ ? tokenDisplayId : DEFAULT_DISPLAY);
final boolean canImeDrawsImeNavBar =
mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
@@ -2709,7 +2707,8 @@
// When the IME switcher dialog is shown, the IME switcher button should be hidden.
if (mMenuController.getSwitchingDialogLocked() != null) return false;
// When we are switching IMEs, the IME switcher button should be hidden.
- if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) {
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
@@ -2788,8 +2787,8 @@
// Note that we still need to update IME status when focusing external display
// that does not support system decoration and fallback to show IME on default
// display since it is intentional behavior.
- if (mCurTokenDisplayId != topFocusedDisplayId
- && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
+ final int tokenDisplayId = getCurTokenDisplayIdLocked();
+ if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
return;
}
mImeWindowVis = vis;
@@ -2852,7 +2851,7 @@
Slog.d(TAG, "IME window vis: " + vis
+ " active: " + (vis & InputMethodService.IME_ACTIVE)
+ " inv: " + (vis & InputMethodService.IME_INVISIBLE)
- + " displayId: " + mCurTokenDisplayId);
+ + " displayId: " + getCurTokenDisplayIdLocked());
}
final IBinder focusedWindowToken = mImeBindingState != null
? mImeBindingState.mFocusedWindow : null;
@@ -2871,15 +2870,17 @@
} else {
vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
}
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var curId = userData.mBindingController.getCurId();
if (mMenuController.getSwitchingDialogLocked() != null
- || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+ || !Objects.equals(curId, getSelectedMethodIdLocked())) {
// When the IME switcher dialog is shown, or we are switching IMEs,
// the back button should be in the default state (as if the IME is not shown).
backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
}
final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
+ mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(),
getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
}
} finally {
@@ -3567,12 +3568,15 @@
"InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ final var bindingController = userData.mBindingController;
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
- getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+ getSelectedMethodIdLocked(),
+ bindingController.getSequenceNumber(),
false /* isInputMethodSuppressingSpellChecker */);
}
final ClientState cs = mClientController.getClient(client.asBinder());
@@ -3664,7 +3668,7 @@
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
+ unverifiedTargetSdkVersion, userData, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3692,7 +3696,7 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
@@ -3705,7 +3709,7 @@
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
- + " userId=" + userId
+ + " userData=" + userData
+ " imeDispatcher=" + imeDispatcher
+ " cs=" + cs);
}
@@ -3724,7 +3728,6 @@
startInputByWinGainedFocus, toolType);
mVisibilityStateComputer.setWindowState(windowToken, windowState);
- final var userData = mUserDataRepository.getOrCreate(userId);
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3742,7 +3745,8 @@
null, null, null, null, -1, false);
}
- mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
+ mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs,
+ editorInfo);
mFocusedWindowPerceptible.put(windowToken, true);
// We want to start input before showing the IME, but after closing
@@ -3781,7 +3785,7 @@
// window token removed.
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
- if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
+ if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
userData.mBindingController.unbindCurrentMethod();
}
}
@@ -3832,10 +3836,10 @@
if (mCurrentUserId != UserHandle.getUserId(uid)) {
return false;
}
- if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal,
- uid,
- getCurIntentLocked().getComponent().getPackageName())) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var curIntent = userData.mBindingController.getCurIntent();
+ if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
+ mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
return true;
}
return false;
@@ -4163,7 +4167,7 @@
}
// This should probably use the caller's display id, but because this is unsupported
// and maintained only for compatibility, there's no point in fixing it.
- curTokenDisplayId = mCurTokenDisplayId;
+ curTokenDisplayId = getCurTokenDisplayIdLocked();
}
return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
});
@@ -4244,8 +4248,9 @@
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
if (!mHwController.getCurrentRequestId().isPresent()
- && userData.mBindingController.supportsStylusHandwriting()) {
+ && bindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
}
}
@@ -4427,9 +4432,10 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
- proto.write(CUR_SEQ, getSequenceNumberLocked());
+ proto.write(CUR_SEQ, bindingController.getSequenceNumber());
proto.write(CUR_CLIENT, Objects.toString(mCurClient));
mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
@@ -4439,13 +4445,13 @@
if (mCurEditorInfo != null) {
mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
- proto.write(CUR_ID, getCurIdLocked());
+ proto.write(CUR_ID, bindingController.getCurId());
mVisibilityStateComputer.dumpDebug(proto, fieldId);
proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
- proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
+ proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked());
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection());
+ proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -4557,7 +4563,8 @@
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
- show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
+ show, mImeBindingState.mFocusedWindow, requestToken,
+ getCurTokenDisplayIdLocked());
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
@@ -4816,10 +4823,11 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- if (userData.mBindingController.supportsStylusHandwriting()
+ final var bindingController = userData.mBindingController;
+ if (bindingController.supportsStylusHandwriting()
&& getCurMethodLocked() != null && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
- mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+ mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked());
} else {
mHwController.reset();
}
@@ -4842,11 +4850,12 @@
return true;
}
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- userData.mBindingController.getCurMethodUid(),
+ bindingController.getCurMethodUid(),
mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
@@ -4896,7 +4905,11 @@
if (mCurClient == null || mCurClient.mClient == null) {
return;
}
- if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(getCurMethodUidLocked())) {
+ // TODO(b/325515685): user data must be retrieved by a userId parameter
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
+ if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
+ bindingController.getCurMethodUid())) {
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
@@ -5141,7 +5154,8 @@
@GuardedBy("ImfLock.class")
void sendOnNavButtonFlagsChangedLocked() {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
+ final var bindingController = userData.mBindingController;
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
@@ -5503,7 +5517,7 @@
@Override
public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
+ InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) {
// Get the device global touch exploration state before lock to avoid deadlock.
final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
.isTouchExplorationEnabled(userId);
@@ -5575,7 +5589,7 @@
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
- if (displayId != mCurTokenDisplayId) {
+ if (displayId != getCurTokenDisplayIdLocked()) {
return false;
}
curHostInputToken = mAutofillController.getCurHostInputToken();
@@ -5626,6 +5640,8 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5647,8 +5663,10 @@
mCurClient.mAccessibilitySessions);
final InputBindResult res = new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
- imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(),
- getSequenceNumberLocked(), false);
+ imeSession, accessibilityInputMethodSessions, /* channel= */ null,
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(),
+ /* isInputMethodSuppressingSpellChecker= */ false);
mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
}
}
@@ -5658,6 +5676,8 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
if (DEBUG) {
@@ -5667,7 +5687,7 @@
// A11yManagerService unbinds the disabled accessibility service. We don't need
// to do it here.
mCurClient.mClient.onUnbindAccessibilityService(
- getSequenceNumberLocked(),
+ bindingController.getSequenceNumber(),
accessibilityConnectionId);
}
// We only have sessions when we bound to an input method. Remove this session
@@ -5877,13 +5897,11 @@
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
p.println(" " + c + ":");
p.println(" client=" + c.mClient);
-
p.println(" fallbackInputConnection="
+ c.mFallbackInputConnection);
p.println(" sessionRequested="
+ c.mSessionRequested);
- p.println(
- " sessionRequestedForAccessibility="
+ p.println(" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
p.println(" curSession=" + c.mCurSession);
p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
@@ -5891,17 +5909,20 @@
p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
- p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
+ p.println(" mCurClient=" + client + " mCurSeq="
+ + bindingController.getSequenceNumber());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(/* prefix= */ " ", p);
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- p.println(" mCurId=" + getCurIdLocked()
- + " mHaveConnection=" + userData.mBindingController.hasMainConnection()
+
+ p.println(" mCurId=" + bindingController.getCurId()
+ + " mHaveConnection=" + bindingController.hasMainConnection()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
- + userData.mBindingController.isVisibleBound());
+ + bindingController.isVisibleBound());
p.println(" mUserDataRepository=");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@@ -5915,9 +5936,9 @@
mUserDataRepository.forAllUserData(userDataDump);
p.println(" mCurToken=" + getCurTokenLocked());
- p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
+ p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked());
p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
- p.println(" mCurIntent=" + getCurIntentLocked());
+ p.println(" mCurIntent=" + bindingController.getCurIntent());
method = getCurMethodLocked();
p.println(" mCurMethod=" + getCurMethodLocked());
p.println(" mEnabledSession=" + mEnabledSession);
@@ -6408,7 +6429,8 @@
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
final var userData = mUserDataRepository.getOrCreate(userId);
- userData.mBindingController.unbindCurrentMethod();
+ final var bindingController = userData.mBindingController;
+ bindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
var toDisable = settings.getEnabledInputMethodList();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
index a8e5e2e..f06643df 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -23,6 +23,7 @@
import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
+import java.util.Arrays;
import java.util.List;
/**
@@ -75,4 +76,61 @@
int size() {
return mMap.size();
}
+
+ @AnyThread
+ @NonNull
+ public InputMethodMap applyAdditionalSubtypes(
+ @NonNull AdditionalSubtypeMap additionalSubtypeMap) {
+ if (additionalSubtypeMap.isEmpty()) {
+ return this;
+ }
+ final int size = size();
+ final ArrayMap<String, InputMethodInfo> newMethodMap = new ArrayMap<>(size);
+ boolean updated = false;
+ for (int i = 0; i < size; ++i) {
+ final var imi = valueAt(i);
+ final var imeId = imi.getId();
+ final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId);
+ if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) {
+ newMethodMap.put(imi.getId(), imi);
+ } else {
+ newMethodMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes));
+ updated = true;
+ }
+ }
+ return updated ? InputMethodMap.of(newMethodMap) : this;
+ }
+
+ /**
+ * Compares the given two {@link InputMethodMap} instances to see if they contain the same data
+ * or not.
+ *
+ * @param map1 {@link InputMethodMap} to be compared with
+ * @param map2 {@link InputMethodMap} to be compared with
+ * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data
+ */
+ @AnyThread
+ static boolean equals(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
+ if (map1 == map2) {
+ return true;
+ }
+ final int size = map1.size();
+ if (size != map2.size()) {
+ return false;
+ }
+ for (int i = 0; i < size; ++i) {
+ final var imi1 = map1.valueAt(i);
+ final var imeId = imi1.getId();
+ final var imi2 = map2.get(imeId);
+ if (imi2 == null) {
+ return false;
+ }
+ final var marshaled1 = InputMethodInfoUtils.marshal(imi1);
+ final var marshaled2 = InputMethodInfoUtils.marshal(imi2);
+ if (!Arrays.equals(marshaled1, marshaled2)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 825cfcb..2b19d3e 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -96,5 +96,10 @@
mUserId = userId;
mBindingController = bindingController;
}
+
+ @Override
+ public String toString() {
+ return "UserData{" + "mUserId=" + mUserId + '}';
+ }
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index a0dbfa0..ec94e2b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -412,10 +412,15 @@
/* package */
synchronized void addTransaction(
ContextHubServiceTransaction transaction) throws IllegalStateException {
+ if (transaction == null) {
+ return;
+ }
+
if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
throw new IllegalStateException("Transaction queue is full (capacity = "
+ MAX_PENDING_REQUESTS + ")");
}
+
mTransactionQueue.add(transaction);
mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
@@ -517,7 +522,10 @@
* the caller has obtained a lock on this ContextHubTransactionManager object.
*/
private void removeTransactionAndStartNext() {
- mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+ if (mTimeoutFuture != null) {
+ mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mTimeoutFuture = null;
+ }
ContextHubServiceTransaction transaction = mTransactionQueue.remove();
transaction.setComplete();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 73647db..e1f8939 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2800,6 +2800,10 @@
final String providerId = route.getProviderId();
final MediaRoute2Provider provider = findProvider(providerId);
if (provider == null) {
+ Slog.w(
+ TAG,
+ "Ignoring transferToRoute due to lack of matching provider for target: "
+ + route);
return;
}
provider.transferToRoute(
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index d25f529..5ea3e70 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -20,6 +20,9 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -31,6 +34,9 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -143,6 +149,8 @@
private final Object mQuotaLock = new Object();
private final Object mRulesLock = new Object();
+ private final boolean mUseMeteredFirewallChains;
+
/** Set of interfaces with active quotas. */
@GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveQuotas = Maps.newHashMap();
@@ -150,9 +158,11 @@
@GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
/** Set of UIDs denied on metered networks. */
+ // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
@GuardedBy("mRulesLock")
private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
/** Set of UIDs allowed on metered networks. */
+ // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
@GuardedBy("mRulesLock")
private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
/** Set of UIDs with cleartext penalties. */
@@ -196,10 +206,32 @@
@GuardedBy("mRulesLock")
private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+ /**
+ * Contains the per-UID firewall rules that are used to allowlist the app from metered-network
+ * restrictions when data saver is enabled.
+ */
+ @GuardedBy("mRulesLock")
+ private final SparseIntArray mUidMeteredFirewallAllowRules = new SparseIntArray();
+
+ /**
+ * Contains the per-UID firewall rules that are used to deny app access to metered networks
+ * due to user action.
+ */
+ @GuardedBy("mRulesLock")
+ private final SparseIntArray mUidMeteredFirewallDenyUserRules = new SparseIntArray();
+
+ /**
+ * Contains the per-UID firewall rules that are used to deny app access to metered networks
+ * due to admin action.
+ */
+ @GuardedBy("mRulesLock")
+ private final SparseIntArray mUidMeteredFirewallDenyAdminRules = new SparseIntArray();
+
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
+ // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
@GuardedBy("mQuotaLock")
private volatile boolean mDataSaverMode;
@@ -217,6 +249,15 @@
mContext = context;
mDeps = deps;
+ mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
+
+ if (mUseMeteredFirewallChains) {
+ // These firewalls are always on and currently ConnectivityService does not allow
+ // changing their enabled state.
+ mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_USER, true);
+ mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_ADMIN, true);
+ }
+
mDaemonHandler = new Handler(FgThread.get().getLooper());
mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
@@ -410,33 +451,39 @@
}
}
- SparseBooleanArray uidRejectOnQuota = null;
- SparseBooleanArray uidAcceptOnQuota = null;
- synchronized (mRulesLock) {
- size = mUidRejectOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
- uidRejectOnQuota = mUidRejectOnMetered;
- mUidRejectOnMetered = new SparseBooleanArray();
- }
+ if (!mUseMeteredFirewallChains) {
+ SparseBooleanArray uidRejectOnQuota = null;
+ SparseBooleanArray uidAcceptOnQuota = null;
+ synchronized (mRulesLock) {
+ size = mUidRejectOnMetered.size();
+ if (size > 0) {
+ if (DBG) {
+ Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
+ }
+ uidRejectOnQuota = mUidRejectOnMetered;
+ mUidRejectOnMetered = new SparseBooleanArray();
+ }
- size = mUidAllowOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
- uidAcceptOnQuota = mUidAllowOnMetered;
- mUidAllowOnMetered = new SparseBooleanArray();
+ size = mUidAllowOnMetered.size();
+ if (size > 0) {
+ if (DBG) {
+ Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
+ }
+ uidAcceptOnQuota = mUidAllowOnMetered;
+ mUidAllowOnMetered = new SparseBooleanArray();
+ }
}
- }
- if (uidRejectOnQuota != null) {
- for (int i = 0; i < uidRejectOnQuota.size(); i++) {
- setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
- uidRejectOnQuota.valueAt(i));
+ if (uidRejectOnQuota != null) {
+ for (int i = 0; i < uidRejectOnQuota.size(); i++) {
+ setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
+ uidRejectOnQuota.valueAt(i));
+ }
}
- }
- if (uidAcceptOnQuota != null) {
- for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
- setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
- uidAcceptOnQuota.valueAt(i));
+ if (uidAcceptOnQuota != null) {
+ for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
+ setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
+ uidAcceptOnQuota.valueAt(i));
+ }
}
}
@@ -459,8 +506,16 @@
syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
+ if (mUseMeteredFirewallChains) {
+ syncFirewallChainLocked(FIREWALL_CHAIN_METERED_ALLOW,
+ FIREWALL_CHAIN_NAME_METERED_ALLOW);
+ syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_USER,
+ FIREWALL_CHAIN_NAME_METERED_DENY_USER);
+ syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_ADMIN,
+ FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN);
+ }
- final int[] chains = {
+ final int[] chainsToEnable = {
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_POWERSAVE,
@@ -469,14 +524,13 @@
FIREWALL_CHAIN_BACKGROUND,
};
- for (int chain : chains) {
+ for (int chain : chainsToEnable) {
if (getFirewallChainState(chain)) {
setFirewallChainEnabled(chain, true);
}
}
}
-
try {
getBatteryStats().noteNetworkStatsEnabled();
} catch (RemoteException e) {
@@ -1077,6 +1131,14 @@
mContext.getSystemService(ConnectivityManager.class)
.setDataSaverEnabled(enable);
mDataSaverMode = enable;
+ if (mUseMeteredFirewallChains) {
+ // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
+ // until ConnectivityService allows manipulation of the data saver mode via
+ // FIREWALL_CHAIN_METERED_ALLOW.
+ synchronized (mRulesLock) {
+ mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
+ }
+ }
return true;
} else {
final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
@@ -1191,9 +1253,9 @@
setFirewallChainState(chain, enable);
}
- final String chainName = getFirewallChainName(chain);
- if (chain == FIREWALL_CHAIN_NONE) {
- throw new IllegalArgumentException("Bad child chain: " + chainName);
+ if (!isValidFirewallChainForSetEnabled(chain)) {
+ throw new IllegalArgumentException("Invalid chain for setFirewallChainEnabled: "
+ + NetworkPolicyLogger.getFirewallChainName(chain));
}
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
@@ -1205,38 +1267,29 @@
}
}
- private String getFirewallChainName(int chain) {
- switch (chain) {
- case FIREWALL_CHAIN_STANDBY:
- return FIREWALL_CHAIN_NAME_STANDBY;
- case FIREWALL_CHAIN_DOZABLE:
- return FIREWALL_CHAIN_NAME_DOZABLE;
- case FIREWALL_CHAIN_POWERSAVE:
- return FIREWALL_CHAIN_NAME_POWERSAVE;
- case FIREWALL_CHAIN_RESTRICTED:
- return FIREWALL_CHAIN_NAME_RESTRICTED;
- case FIREWALL_CHAIN_LOW_POWER_STANDBY:
- return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
- case FIREWALL_CHAIN_BACKGROUND:
- return FIREWALL_CHAIN_NAME_BACKGROUND;
- default:
- throw new IllegalArgumentException("Bad child chain: " + chain);
- }
+ private boolean isValidFirewallChainForSetEnabled(int chain) {
+ return switch (chain) {
+ case FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_BACKGROUND -> true;
+ // METERED_* firewall chains are not yet supported by
+ // ConnectivityService#setFirewallChainEnabled.
+ default -> false;
+ };
}
private int getFirewallType(int chain) {
switch (chain) {
case FIREWALL_CHAIN_STANDBY:
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ case FIREWALL_CHAIN_METERED_DENY_USER:
return FIREWALL_DENYLIST;
case FIREWALL_CHAIN_DOZABLE:
- return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_POWERSAVE:
- return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_RESTRICTED:
- return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_LOW_POWER_STANDBY:
- return FIREWALL_ALLOWLIST;
case FIREWALL_CHAIN_BACKGROUND:
+ case FIREWALL_CHAIN_METERED_ALLOW:
return FIREWALL_ALLOWLIST;
default:
return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
@@ -1360,6 +1413,12 @@
return mUidFirewallLowPowerStandbyRules;
case FIREWALL_CHAIN_BACKGROUND:
return mUidFirewallBackgroundRules;
+ case FIREWALL_CHAIN_METERED_ALLOW:
+ return mUidMeteredFirewallAllowRules;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return mUidMeteredFirewallDenyUserRules;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return mUidMeteredFirewallDenyAdminRules;
case FIREWALL_CHAIN_NONE:
return mUidFirewallRules;
default:
@@ -1378,6 +1437,10 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ pw.println("Flags:");
+ pw.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains);
+ pw.println();
+
synchronized (mQuotaLock) {
pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
@@ -1416,6 +1479,27 @@
pw.print("UID firewall background chain enabled: ");
pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
+
+ pw.print("UID firewall metered allow chain enabled (Data saver mode): ");
+ // getFirewallChainState should maintain a duplicated state from mDataSaverMode when
+ // mUseMeteredFirewallChains is enabled.
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_ALLOW,
+ mUidMeteredFirewallAllowRules);
+
+ pw.print("UID firewall metered deny_user chain enabled (always-on): ");
+ // This always-on state should be reflected by getFirewallChainState when
+ // mUseMeteredFirewallChains is enabled.
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_USER,
+ mUidMeteredFirewallDenyUserRules);
+
+ pw.print("UID firewall metered deny_admin chain enabled (always-on): ");
+ // This always-on state should be reflected by getFirewallChainState when
+ // mUseMeteredFirewallChains is enabled.
+ pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN));
+ dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN,
+ mUidMeteredFirewallDenyAdminRules);
}
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1520,14 +1604,40 @@
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
return true;
}
- if (mUidRejectOnMetered.get(uid)) {
- if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
- + " in the background");
- return true;
- }
- if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
- if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
- return true;
+ if (mUseMeteredFirewallChains) {
+ if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER)
+ && mUidMeteredFirewallDenyUserRules.get(uid) == FIREWALL_RULE_DENY) {
+ if (DBG) {
+ Slog.d(TAG, "Uid " + uid + " restricted because of user-restricted metered"
+ + " data in the background");
+ }
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN)
+ && mUidMeteredFirewallDenyAdminRules.get(uid) == FIREWALL_RULE_DENY) {
+ if (DBG) {
+ Slog.d(TAG, "Uid " + uid + " restricted because of admin-restricted metered"
+ + " data in the background");
+ }
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW)
+ && mUidMeteredFirewallAllowRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+ return true;
+ }
+ } else {
+ if (mUidRejectOnMetered.get(uid)) {
+ if (DBG) {
+ Slog.d(TAG, "Uid " + uid
+ + " restricted because of no metered data in the background");
+ }
+ return true;
+ }
+ if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 8e2d778..681aa8a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -19,6 +19,9 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -28,6 +31,9 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -379,7 +385,7 @@
return "Interfaces of netId=" + netId + " changed to " + newIfaces;
}
- private static String getFirewallChainName(int chain) {
+ static String getFirewallChainName(int chain) {
switch (chain) {
case FIREWALL_CHAIN_DOZABLE:
return FIREWALL_CHAIN_NAME_DOZABLE;
@@ -393,6 +399,12 @@
return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
case FIREWALL_CHAIN_BACKGROUND:
return FIREWALL_CHAIN_NAME_BACKGROUND;
+ case FIREWALL_CHAIN_METERED_ALLOW:
+ return FIREWALL_CHAIN_NAME_METERED_ALLOW;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return FIREWALL_CHAIN_NAME_METERED_DENY_USER;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
default:
return String.valueOf(chain);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 22f5332..c60ac3a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -60,6 +60,9 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -514,6 +517,12 @@
*/
private boolean mBackgroundNetworkRestricted;
+ /**
+ * Whether or not metered firewall chains should be used for uid policy controlling access to
+ * metered networks.
+ */
+ private boolean mUseMeteredFirewallChains;
+
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
@@ -997,6 +1006,8 @@
mAppStandby = LocalServices.getService(AppStandbyInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
+
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
updatePowerSaveAllowlistUL();
@@ -4030,8 +4041,10 @@
fout.println();
fout.println("Flags:");
- fout.println("Network blocked for TOP_SLEEPING and above: "
+ fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": "
+ mBackgroundNetworkRestricted);
+ fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": "
+ + mUseMeteredFirewallChains);
fout.println();
fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
@@ -5373,23 +5386,44 @@
postUidRulesChangedMsg(uid, uidRules);
}
- // Note that the conditionals below are for avoiding unnecessary calls to netd.
- // TODO: Measure the performance for doing a no-op call to netd so that we can
- // remove the conditionals to simplify the logic below. We can also further reduce
- // some calls to netd if they turn out to be costly.
- final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
- | BLOCKED_METERED_REASON_USER_RESTRICTED;
- if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
- || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
- setMeteredNetworkDenylist(uid,
- (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
- }
- final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
- | ALLOWED_METERED_REASON_USER_EXEMPTED;
- if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
- || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
- setMeteredNetworkAllowlist(uid,
- (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+ if (mUseMeteredFirewallChains) {
+ if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_ADMIN_DISABLED)
+ != BLOCKED_REASON_NONE) {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DENY);
+ } else {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DEFAULT);
+ }
+ if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED)
+ != BLOCKED_REASON_NONE) {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);
+ } else {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DEFAULT);
+ }
+ if ((newAllowedReasons & (ALLOWED_METERED_REASON_FOREGROUND
+ | ALLOWED_METERED_REASON_USER_EXEMPTED)) != ALLOWED_REASON_NONE) {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);
+ } else {
+ setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DEFAULT);
+ }
+ } else {
+ // Note that the conditionals below are for avoiding unnecessary calls to netd.
+ // TODO: Measure the performance for doing a no-op call to netd so that we can
+ // remove the conditionals to simplify the logic below. We can also further reduce
+ // some calls to netd if they turn out to be costly.
+ final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+ | BLOCKED_METERED_REASON_USER_RESTRICTED;
+ if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
+ || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
+ setMeteredNetworkDenylist(uid,
+ (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
+ }
+ final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
+ | ALLOWED_METERED_REASON_USER_EXEMPTED;
+ if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
+ || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
+ setMeteredNetworkAllowlist(uid,
+ (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+ }
}
}
@@ -6149,6 +6183,8 @@
} else if (chain == FIREWALL_CHAIN_BACKGROUND) {
mUidFirewallBackgroundRules.put(uid, rule);
}
+ // Note that we do not need keep a separate cache of uid rules for chains that we do
+ // not call #setUidFirewallRulesUL for.
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
@@ -6206,10 +6242,19 @@
FIREWALL_RULE_DEFAULT);
mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
FIREWALL_RULE_DEFAULT);
- mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
- mLogger.meteredAllowlistChanged(uid, false);
- mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
- mLogger.meteredDenylistChanged(uid, false);
+ if (mUseMeteredFirewallChains) {
+ mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid,
+ FIREWALL_RULE_DEFAULT);
+ mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid,
+ FIREWALL_RULE_DEFAULT);
+ mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid,
+ FIREWALL_RULE_DEFAULT);
+ } else {
+ mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
+ mLogger.meteredAllowlistChanged(uid, false);
+ mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
+ mLogger.meteredDenylistChanged(uid, false);
+ }
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index d9491de..e986dd8 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -7,3 +7,13 @@
description: "Block network access for apps in a low importance background state"
bug: "304347838"
}
+
+flag {
+ name: "use_metered_firewall_chains"
+ namespace: "backstage_power"
+ description: "Use metered firewall chains to control access to metered networks"
+ bug: "336693007"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index bf49671..13429db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -22,12 +22,14 @@
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.media.audio.Flags.focusExclusiveWithRecording;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import android.Manifest.permission;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -135,7 +137,7 @@
private LogicalLight mAttentionLight;
private final boolean mUseAttentionLight;
- boolean mHasLight = true;
+ boolean mHasLight;
private final SettingsObserver mSettingsObserver;
@@ -149,7 +151,7 @@
private boolean mInCallStateOffHook = false;
private boolean mScreenOn = true;
private boolean mUserPresent = false;
- boolean mNotificationPulseEnabled;
+ private boolean mNotificationPulseEnabled;
private final Uri mInCallNotificationUri;
private final AudioAttributes mInCallNotificationAudioAttributes;
private final float mInCallNotificationVolume;
@@ -223,7 +225,10 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+ record -> mPackageManager.checkPermission(
+ permission.RECEIVE_EMERGENCY_BROADCAST,
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED);
return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -231,14 +236,17 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
- appStrategy);
+ appStrategy, appStrategy.mExemptionProvider);
} else {
return new StrategyPerApp(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+ record -> mPackageManager.checkPermission(
+ permission.RECEIVE_EMERGENCY_BROADCAST,
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED);
}
}
@@ -305,6 +313,13 @@
}
private void loadUserSettings() {
+ boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
+ if (mNotificationPulseEnabled != pulseEnabled) {
+ mNotificationPulseEnabled = pulseEnabled;
+ updateLightsLocked();
+ }
+
if (Flags.politeNotifications()) {
try {
mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
@@ -874,6 +889,9 @@
boolean canShowLightsLocked(final NotificationRecord record, final Signals signals,
boolean aboveThreshold) {
+ if (!mSystemReady) {
+ return false;
+ }
// device lacks light
if (!mHasLight) {
return false;
@@ -1088,6 +1106,11 @@
}
}
+ // Returns true if a notification should be exempted from attenuation
+ private interface ExemptionProvider {
+ boolean isExempted(NotificationRecord record);
+ }
+
@VisibleForTesting
abstract static class PolitenessStrategy {
static final int POLITE_STATE_DEFAULT = 0;
@@ -1118,8 +1141,10 @@
protected boolean mIsActive = true;
+ protected final ExemptionProvider mExemptionProvider;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted) {
+ int volumeMuted, ExemptionProvider exemptionProvider) {
mVolumeStates = new HashMap<>();
mLastUpdatedTimestampByPackage = new HashMap<>();
@@ -1127,6 +1152,7 @@
this.mTimeoutMuted = timeoutMuted;
this.mVolumePolite = volumePolite / 100.0f;
this.mVolumeMuted = volumeMuted / 100.0f;
+ this.mExemptionProvider = exemptionProvider;
}
abstract void onNotificationPosted(NotificationRecord record);
@@ -1284,8 +1310,8 @@
private final int mMaxPostedForReset;
public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, int maxPosted) {
- super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
+ super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
mNumPosted = new HashMap<>();
mMaxPostedForReset = maxPosted;
@@ -1306,7 +1332,12 @@
final String key = getChannelKey(record);
@PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ @PolitenessState int nextState;
+ if (Flags.politeNotificationsAttnUpdate()) {
+ nextState = getNextState(currState, timeSinceLastNotif, record);
+ } else {
+ nextState = getNextState(currState, timeSinceLastNotif);
+ }
// Reset to default state if number of posted notifications exceed this value when muted
int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
@@ -1324,6 +1355,14 @@
mVolumeStates.put(key, nextState);
}
+ @PolitenessState int getNextState(@PolitenessState final int currState,
+ final long timeSinceLastNotif, final NotificationRecord record) {
+ if (mExemptionProvider.isExempted(record)) {
+ return POLITE_STATE_DEFAULT;
+ }
+ return getNextState(currState, timeSinceLastNotif);
+ }
+
@Override
public void onUserInteraction(final NotificationRecord record) {
super.onUserInteraction(record);
@@ -1344,8 +1383,9 @@
private long mLastAvalancheTriggerTimestamp = 0;
StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
- super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
+ ExemptionProvider exemptionProvider) {
+ super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
mTimeoutAvalanche = timeoutAvalanche;
mAppStrategy = appStrategy;
@@ -1518,7 +1558,7 @@
return true;
}
- return false;
+ return mExemptionProvider.isExempted(record);
}
private boolean isAvalancheExempted(final NotificationRecord record) {
@@ -1721,8 +1761,6 @@
void setLights(LogicalLight light) {
mNotificationLight = light;
mAttentionLight = light;
- mNotificationPulseEnabled = true;
- mHasLight = true;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 61054a9..44e7694 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -593,6 +593,8 @@
static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis();
+ static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis();
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -2637,27 +2639,48 @@
* Cleanup broadcast receivers change listeners.
*/
public void onDestroy() {
- getContext().unregisterReceiver(mIntentReceiver);
- getContext().unregisterReceiver(mPackageIntentReceiver);
- if (Flags.allNotifsNeedTtl()) {
- mTtlHelper.destroy();
- } else {
- getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ if (mIntentReceiver != null) {
+ getContext().unregisterReceiver(mIntentReceiver);
}
- getContext().unregisterReceiver(mRestoreReceiver);
- getContext().unregisterReceiver(mLocaleChangeReceiver);
-
- mSettingsObserver.destroy();
- mRoleObserver.destroy();
+ if (mPackageIntentReceiver != null) {
+ getContext().unregisterReceiver(mPackageIntentReceiver);
+ }
+ if (Flags.allNotifsNeedTtl()) {
+ if (mTtlHelper != null) {
+ mTtlHelper.destroy();
+ }
+ } else {
+ if (mNotificationTimeoutReceiver != null) {
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ }
+ }
+ if (mRestoreReceiver != null) {
+ getContext().unregisterReceiver(mRestoreReceiver);
+ }
+ if (mLocaleChangeReceiver != null) {
+ getContext().unregisterReceiver(mLocaleChangeReceiver);
+ }
+ if (mSettingsObserver != null) {
+ mSettingsObserver.destroy();
+ }
+ if (mRoleObserver != null) {
+ mRoleObserver.destroy();
+ }
if (mShortcutHelper != null) {
mShortcutHelper.destroy();
}
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
- mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
- mAppOps.stopWatchingMode(mAppOpsListener);
- mAlarmManager.cancelAll();
+ if (mStatsManager != null) {
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+ }
+ if (mAppOps != null) {
+ mAppOps.stopWatchingMode(mAppOpsListener);
+ }
+ if (mAlarmManager != null) {
+ mAlarmManager.cancelAll();
+ }
}
protected String[] getStringArrayResource(int key) {
@@ -5809,7 +5832,16 @@
@Override
public ComponentName getEffectsSuppressor() {
- return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null;
+ ComponentName suppressor = !mEffectsSuppressors.isEmpty()
+ ? mEffectsSuppressors.get(0)
+ : null;
+ if (isCallerSystemOrSystemUiOrShell() || suppressor == null
+ || mPackageManagerInternal.isSameApp(suppressor.getPackageName(),
+ Binder.getCallingUid(), UserHandle.getUserId(Binder.getCallingUid()))) {
+ return suppressor;
+ }
+
+ return null;
}
@Override
@@ -7202,7 +7234,15 @@
callingUid, userId, true, false, "cancelNotificationWithTag", pkg);
// ensure opPkg is delegate if does not match pkg
- int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+
+ int uid = INVALID_UID;
+
+ try {
+ uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+ } catch (NameNotFoundException e) {
+ // package either never existed so there's no posted notification or it's being
+ // uninstalled so we'll be cleaning it up soon. log and return immediately below.
+ }
if (uid == INVALID_UID) {
Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification "
@@ -7296,7 +7336,13 @@
// Can throw a SecurityException if the calling uid doesn't have permission to post
// as "pkg"
- final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+ int notificationUid = INVALID_UID;
+
+ try {
+ notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+ } catch (NameNotFoundException e) {
+ // not great - throw immediately below
+ }
if (notificationUid == INVALID_UID) {
throw new SecurityException("Caller " + opPkg + ":" + callingUid
@@ -7722,6 +7768,9 @@
return true;
}
// Check if an app has been given system exemption
+ if (ai.uid == Process.SYSTEM_UID) {
+ return false;
+ }
return mAppOps.checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
ai.packageName) == MODE_ALLOWED;
@@ -7850,7 +7899,8 @@
}
@VisibleForTesting
- int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
+ int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId)
+ throws NameNotFoundException {
if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
@@ -7861,12 +7911,8 @@
return callingUid;
}
- int targetUid = INVALID_UID;
- try {
- targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
- } catch (NameNotFoundException e) {
- /* ignore, handled by caller */
- }
+ int targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
+
// posted from app A on behalf of app B
if (isCallerAndroid(callingPkg, callingUid)
|| mPreferencesHelper.isDelegateAllowed(
@@ -8016,6 +8062,13 @@
return false;
}
+ if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0
+ && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) {
+ Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey());
+ mUsageStats.registerTooOldBlocked(r);
+ return false;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e960f4b..c09077e 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -257,6 +257,14 @@
}
}
+ public synchronized void registerTooOldBlocked(NotificationRecord notification) {
+ AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
+ for (AggregatedStats stats : aggregatedStatsArray) {
+ stats.numTooOld++;
+ }
+ releaseAggregatedStatsLocked(aggregatedStatsArray);
+ }
+
@GuardedBy("this")
private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
return getAggregatedStatsLocked(record.getSbn().getPackageName());
@@ -405,6 +413,7 @@
public int numUndecoratedRemoteViews;
public long mLastAccessTime;
public int numImagesRemoved;
+ public int numTooOld;
public AggregatedStats(Context context, String key) {
this.key = key;
@@ -535,6 +544,7 @@
maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved));
+ maybeCount("not_too_old", (numTooOld - previous.numTooOld));
noisyImportance.maybeCount(previous.noisyImportance);
quietImportance.maybeCount(previous.quietImportance);
finalImportance.maybeCount(previous.finalImportance);
@@ -570,6 +580,7 @@
previous.numAlertViolations = numAlertViolations;
previous.numQuotaViolations = numQuotaViolations;
previous.numImagesRemoved = numImagesRemoved;
+ previous.numTooOld = numTooOld;
noisyImportance.update(previous.noisyImportance);
quietImportance.update(previous.quietImportance);
finalImportance.update(previous.finalImportance);
@@ -679,6 +690,8 @@
output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
output.append(indentPlusTwo);
output.append("numImagesRemoved=").append(numImagesRemoved).append("\n");
+ output.append(indentPlusTwo);
+ output.append("numTooOld=").append(numTooOld).append("\n");
output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
@@ -725,6 +738,7 @@
maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
maybePut(dump, "numAlertViolations", numAlertViolations);
maybePut(dump, "numImagesRemoved", numImagesRemoved);
+ maybePut(dump, "numTooOld", numTooOld);
noisyImportance.maybePut(dump, previous.noisyImportance);
quietImportance.maybePut(dump, previous.quietImportance);
finalImportance.maybePut(dump, previous.finalImportance);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 9dcca49..bf6b652 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -135,3 +135,10 @@
description: "This flag controls which signal is used to handle a user switch system event"
bug: "337077643"
}
+
+flag {
+ name: "reject_old_notifications"
+ namespace: "systemui"
+ description: "This flag does not allow notifications older than 2 weeks old to be posted"
+ bug: "339833083"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 681dd0b..96ab2cc 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -33,6 +33,7 @@
import android.os.BadParcelableException;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -41,7 +42,10 @@
import android.system.Os;
import android.util.Log;
+import com.android.internal.infra.AndroidFuture;
+
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
/**
* Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
@@ -78,16 +82,16 @@
if (canMarshall(obj) || obj instanceof CursorWindow) {
continue;
}
-
- if (obj instanceof ParcelFileDescriptor) {
+ if (obj instanceof Bundle) {
+ sanitizeInferenceParams((Bundle) obj);
+ } else if (obj instanceof ParcelFileDescriptor) {
validatePfdReadOnly((ParcelFileDescriptor) obj);
} else if (obj instanceof SharedMemory) {
((SharedMemory) obj).setProtect(PROT_READ);
} else if (obj instanceof Bitmap) {
- if (((Bitmap) obj).isMutable()) {
- throw new BadParcelableException(
- "Encountered a mutable Bitmap in the Bundle at key : " + key);
- }
+ validateBitmap((Bitmap) obj);
+ } else if (obj instanceof Parcelable[]) {
+ validateParcelableArray((Parcelable[]) obj);
} else {
throw new BadParcelableException(
"Unsupported Parcelable type encountered in the Bundle: "
@@ -125,20 +129,20 @@
continue;
}
- if (obj instanceof ParcelFileDescriptor) {
+ if (obj instanceof Bundle) {
+ sanitizeResponseParams((Bundle) obj);
+ } else if (obj instanceof ParcelFileDescriptor) {
validatePfdReadOnly((ParcelFileDescriptor) obj);
} else if (obj instanceof Bitmap) {
- if (((Bitmap) obj).isMutable()) {
- throw new BadParcelableException(
- "Encountered a mutable Bitmap in the Bundle at key : " + key);
- }
+ validateBitmap((Bitmap) obj);
+ } else if (obj instanceof Parcelable[]) {
+ validateParcelableArray((Parcelable[]) obj);
} else {
throw new BadParcelableException(
"Unsupported Parcelable type encountered in the Bundle: "
+ obj.getClass().getSimpleName());
}
}
- Log.e(TAG, "validateResponseParams : Finished");
}
/**
@@ -183,7 +187,8 @@
public static IStreamingResponseCallback wrapWithValidation(
IStreamingResponseCallback streamingResponseCallback,
- Executor resourceClosingExecutor) {
+ Executor resourceClosingExecutor,
+ AndroidFuture future) {
return new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(Bundle processedResult) throws RemoteException {
@@ -203,6 +208,7 @@
streamingResponseCallback.onSuccess(resultBundle);
} finally {
resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+ future.complete(null);
}
}
@@ -210,6 +216,7 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) throws RemoteException {
streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
+ future.completeExceptionally(new TimeoutException());
}
@Override
@@ -237,7 +244,8 @@
}
public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
- Executor resourceClosingExecutor) {
+ Executor resourceClosingExecutor,
+ AndroidFuture future) {
return new IResponseCallback.Stub() {
@Override
public void onSuccess(Bundle resultBundle)
@@ -247,6 +255,7 @@
responseCallback.onSuccess(resultBundle);
} finally {
resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+ future.complete(null);
}
}
@@ -254,6 +263,7 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) throws RemoteException {
responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ future.completeExceptionally(new TimeoutException());
}
@Override
@@ -280,17 +290,20 @@
}
- public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback) {
+ public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
+ AndroidFuture future) {
return new ITokenInfoCallback.Stub() {
@Override
public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
responseCallback.onSuccess(tokenInfo);
+ future.complete(null);
}
@Override
public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
throws RemoteException {
responseCallback.onFailure(errorCode, errorMessage, errorParams);
+ future.completeExceptionally(new TimeoutException());
}
};
}
@@ -310,6 +323,26 @@
}
}
+ private static void validateParcelableArray(Parcelable[] parcelables) {
+ if (parcelables.length > 0
+ && parcelables[0] instanceof ParcelFileDescriptor) {
+ // Safe to cast
+ validatePfdsReadOnly(parcelables);
+ } else if (parcelables.length > 0
+ && parcelables[0] instanceof Bitmap) {
+ validateBitmapsImmutable(parcelables);
+ } else {
+ throw new BadParcelableException(
+ "Could not cast to any known parcelable array");
+ }
+ }
+
+ public static void validatePfdsReadOnly(Parcelable[] pfds) {
+ for (Parcelable pfd : pfds) {
+ validatePfdReadOnly((ParcelFileDescriptor) pfd);
+ }
+ }
+
public static void validatePfdReadOnly(ParcelFileDescriptor pfd) {
if (pfd == null) {
return;
@@ -326,6 +359,19 @@
}
}
+ private static void validateBitmap(Bitmap obj) {
+ if (obj.isMutable()) {
+ throw new BadParcelableException(
+ "Encountered a mutable Bitmap in the Bundle at key : " + obj);
+ }
+ }
+
+ private static void validateBitmapsImmutable(Parcelable[] bitmaps) {
+ for (Parcelable bitmap : bitmaps) {
+ validateBitmap((Bitmap) bitmap);
+ }
+ }
+
public static void tryCloseResource(Bundle bundle) {
if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) {
return;
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 99401a1..b2e861c 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,6 +16,10 @@
package com.android.server.ondeviceintelligence;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
+
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
@@ -29,6 +33,7 @@
import android.app.AppGlobals;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
@@ -41,6 +46,7 @@
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -59,6 +65,7 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
@@ -77,13 +84,17 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* This is the system service for handling calls on the
@@ -105,12 +116,20 @@
/** Handler message to {@link #resetTemporaryServices()} */
private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+ /** Handler message to clean up temporary broadcast keys. */
+ private static final int MSG_RESET_BROADCAST_KEYS = 1;
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
+ private static final String SYSTEM_PACKAGE = "android";
+
+
private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
private final Executor callbackExecutor = Executors.newCachedThreadPool();
+ private final Executor broadcastExecutor = Executors.newCachedThreadPool();
+
private final Context mContext;
protected final Object mLock = new Object();
@@ -122,12 +141,17 @@
@GuardedBy("mLock")
private String[] mTemporaryServiceNames;
+ @GuardedBy("mLock")
+ private String[] mTemporaryBroadcastKeys;
+ @GuardedBy("mLock")
+ private String mBroadcastPackageName;
/**
* Handler used to reset the temporary service names.
*/
- @GuardedBy("mLock")
private Handler mTemporaryHandler;
+ private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
+
public OnDeviceIntelligenceManagerService(Context context) {
super(context);
@@ -187,8 +211,16 @@
return;
}
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.getVersion(remoteCallback));
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getVersion(new RemoteCallback(
+ result -> {
+ remoteCallback.sendResult(result);
+ future.complete(null);
+ }));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
}
@Override
@@ -208,8 +240,25 @@
}
ensureRemoteIntelligenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.getFeature(callerUid, id, featureCallback));
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getFeature(callerUid, id, new IFeatureCallback.Stub() {
+ @Override
+ public void onSuccess(Feature result) throws RemoteException {
+ featureCallback.onSuccess(result);
+ future.complete(null);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) throws RemoteException {
+ featureCallback.onFailure(errorCode, errorMessage, errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
}
@Override
@@ -229,9 +278,29 @@
}
ensureRemoteIntelligenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.listFeatures(callerUid,
- listFeaturesCallback));
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.listFeatures(callerUid,
+ new IListFeaturesCallback.Stub() {
+ @Override
+ public void onSuccess(List<Feature> result)
+ throws RemoteException {
+ listFeaturesCallback.onSuccess(result);
+ future.complete(null);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams)
+ throws RemoteException {
+ listFeaturesCallback.onFailure(errorCode, errorMessage,
+ errorParams);
+ future.completeExceptionally(new TimeoutException());
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
}
@Override
@@ -253,9 +322,29 @@
}
ensureRemoteIntelligenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.getFeatureDetails(callerUid, feature,
- featureDetailsCallback));
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.getFeatureDetails(callerUid, feature,
+ new IFeatureDetailsCallback.Stub() {
+ @Override
+ public void onSuccess(FeatureDetails result)
+ throws RemoteException {
+ future.complete(null);
+ featureDetailsCallback.onSuccess(result);
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams)
+ throws RemoteException {
+ future.completeExceptionally(null);
+ featureDetailsCallback.onFailure(errorCode,
+ errorMessage, errorParams);
+ }
+ });
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
}
@Override
@@ -276,10 +365,20 @@
}
ensureRemoteIntelligenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- mRemoteOnDeviceIntelligenceService.run(
- service -> service.requestFeatureDownload(callerUid, feature,
- wrapCancellationFuture(cancellationSignalFuture),
- downloadCallback));
+ mRemoteOnDeviceIntelligenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ ListenableDownloadCallback listenableDownloadCallback =
+ new ListenableDownloadCallback(
+ downloadCallback,
+ mMainHandler, future, getIdleTimeoutMs());
+ service.requestFeatureDownload(callerUid, feature,
+ wrapCancellationFuture(cancellationSignalFuture),
+ listenableDownloadCallback);
+ return future; // this future has no timeout because, actual download
+ // might take long, but we fail early if there is no progress callbacks.
+ }
+ );
}
@@ -306,11 +405,15 @@
}
ensureRemoteInferenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.post(
- service -> service.requestTokenInfo(callerUid, feature,
- request,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapWithValidation(tokenInfoCallback)));
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.requestTokenInfo(callerUid, feature,
+ request,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapWithValidation(tokenInfoCallback, future));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
resourceClosingExecutor);
} finally {
@@ -345,13 +448,18 @@
}
ensureRemoteInferenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.post(
- service -> service.processRequest(callerUid, feature,
- request,
- requestType,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapProcessingFuture(processingSignalFuture),
- wrapWithValidation(responseCallback, resourceClosingExecutor)));
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.processRequest(callerUid, feature,
+ request,
+ requestType,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapProcessingFuture(processingSignalFuture),
+ wrapWithValidation(responseCallback,
+ resourceClosingExecutor, future));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
resourceClosingExecutor);
} finally {
@@ -385,13 +493,18 @@
}
ensureRemoteInferenceServiceInitialized();
int callerUid = Binder.getCallingUid();
- result = mRemoteInferenceService.post(
- service -> service.processRequestStreaming(callerUid,
- feature,
- request, requestType,
- wrapCancellationFuture(cancellationSignalFuture),
- wrapProcessingFuture(processingSignalFuture),
- streamingCallback));
+ result = mRemoteInferenceService.postAsync(
+ service -> {
+ AndroidFuture future = new AndroidFuture();
+ service.processRequestStreaming(callerUid,
+ feature,
+ request, requestType,
+ wrapCancellationFuture(cancellationSignalFuture),
+ wrapProcessingFuture(processingSignalFuture),
+ wrapWithValidation(streamingCallback,
+ resourceClosingExecutor, future));
+ return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+ });
result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
resourceClosingExecutor);
} finally {
@@ -482,6 +595,8 @@
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.run(
IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
+ broadcastExecutor.execute(
+ () -> registerModelLoadingBroadcasts(service));
service.registerRemoteStorageService(
getIRemoteStorageService());
} catch (RemoteException ex) {
@@ -493,6 +608,56 @@
}
}
+ private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
+ String[] modelBroadcastKeys;
+ try {
+ modelBroadcastKeys = getBroadcastKeys();
+ } catch (Resources.NotFoundException e) {
+ Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured.");
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true);
+ try {
+ service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() {
+ @Override
+ public void onSuccess(PersistableBundle statusParams) {
+ Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) {
+ String modelLoadedBroadcastKey = modelBroadcastKeys[0];
+ if (modelLoadedBroadcastKey != null
+ && !modelLoadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelLoadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) {
+ String modelUnloadedBroadcastKey = modelBroadcastKeys[1];
+ if (modelUnloadedBroadcastKey != null
+ && !modelUnloadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelUnloadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage) {
+ Slog.e(TAG, "Failed to register model loading callback with status code",
+ new OnDeviceIntelligenceException(errorCode, errorMessage));
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register model loading callback with status code", e);
+ }
+ }
+
@NonNull
private IRemoteStorageService.Stub getIRemoteStorageService() {
return new IRemoteStorageService.Stub() {
@@ -629,6 +794,20 @@
R.string.config_defaultOnDeviceSandboxedInferenceService)};
}
+ protected String[] getBroadcastKeys() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) {
+ return mTemporaryBroadcastKeys;
+ }
+ }
+
+ return new String[]{mContext.getResources().getString(
+ R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey),
+ mContext.getResources().getString(
+ R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)};
+ }
+
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
Objects.requireNonNull(componentNames);
@@ -645,25 +824,26 @@
mRemoteOnDeviceIntelligenceService.unbind();
mRemoteOnDeviceIntelligenceService = null;
}
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- synchronized (mLock) {
- resetTemporaryServices();
- }
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- };
- } else {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- }
if (durationMs != -1) {
- mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE,
+ durationMs);
+ }
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName,
+ int durationMs) {
+ Objects.requireNonNull(broadcastKeys);
+ enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryBroadcastKeys = broadcastKeys;
+ mBroadcastPackageName = receiverPackageName;
+ if (durationMs != -1) {
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs);
}
}
}
@@ -751,4 +931,34 @@
}
}
}
+
+ private synchronized Handler getTemporaryHandler() {
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else if (msg.what == MSG_RESET_BROADCAST_KEYS) {
+ synchronized (mLock) {
+ mTemporaryBroadcastKeys = null;
+ mBroadcastPackageName = SYSTEM_PACKAGE;
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ }
+
+ return mTemporaryHandler;
+ }
+
+ private long getIdleTimeoutMs() {
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
+ mContext.getUserId());
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index a76d8a3..5744b5c 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -43,6 +43,8 @@
return setTemporaryServices();
case "get-services":
return getConfiguredServices();
+ case "set-model-broadcasts":
+ return setBroadcastKeys();
default:
return handleDefaultCommands(cmd);
}
@@ -62,12 +64,18 @@
pw.println(" To reset, call without any arguments.");
pw.println(" get-services To get the names of services that are currently being used.");
+ pw.println(
+ " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] "
+ + "[ReceiverPackageName] "
+ + "[DURATION] To set the names of broadcast intent keys that are to be "
+ + "emitted for cts tests.");
}
private int setTemporaryServices() {
final PrintWriter out = getOutPrintWriter();
final String intelligenceServiceName = getNextArg();
final String inferenceServiceName = getNextArg();
+
if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
&& inferenceServiceName == null) {
mService.resetTemporaryServices();
@@ -79,7 +87,8 @@
Objects.requireNonNull(inferenceServiceName);
final int duration = Integer.parseInt(getNextArgRequired());
mService.setTemporaryServices(
- new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+ new String[]{intelligenceServiceName, inferenceServiceName},
+ duration);
out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ " for " + duration + "ms");
@@ -93,4 +102,22 @@
+ " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
return 0;
}
+
+ private int setBroadcastKeys() {
+ final PrintWriter out = getOutPrintWriter();
+ final String modelLoadedKey = getNextArgRequired();
+ final String modelUnloadedKey = getNextArgRequired();
+ final String receiverPackageName = getNextArg();
+
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setModelBroadcastKeys(
+ new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration);
+ out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to "
+ + modelLoadedKey
+ + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey
+ + "\n and Package name set to : " + receiverPackageName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index 48258d7..ac9747a 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -22,17 +22,21 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
import com.android.internal.infra.ServiceConnector;
+import java.util.concurrent.TimeUnit;
+
/**
* Manages the connection to the remote on-device intelligence service. Also, handles unbinding
* logic set by the service implementation via a Secure Settings flag.
*/
public class RemoteOnDeviceIntelligenceService extends
ServiceConnector.Impl<IOnDeviceIntelligenceService> {
+ private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4);
private static final String TAG =
RemoteOnDeviceIntelligenceService.class.getSimpleName();
@@ -48,9 +52,15 @@
}
@Override
+ protected long getRequestTimeoutMs() {
+ return LONG_TIMEOUT;
+ }
+
+ @Override
protected long getAutoDisconnectTimeoutMs() {
- // Disable automatic unbinding.
- // TODO: add logic to fetch this flag via SecureSettings.
- return -1;
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30),
+ mContext.getUserId());
}
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 69ba1d2..18b1383 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,24 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import com.android.internal.infra.ServiceConnector;
+import java.util.concurrent.TimeUnit;
+
/**
- * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles
+ * unbinding
* logic set by the service implementation via a SecureSettings flag.
*/
public class RemoteOnDeviceSandboxedInferenceService extends
ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
+ private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1);
+
/**
* Creates an instance of {@link ServiceConnector}
*
@@ -54,11 +60,17 @@
connect();
}
+ @Override
+ protected long getRequestTimeoutMs() {
+ return LONG_TIMEOUT;
+ }
+
@Override
protected long getAutoDisconnectTimeoutMs() {
- // Disable automatic unbinding.
- // TODO: add logic to fetch this flag via SecureSettings.
- return -1;
+ return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30),
+ mContext.getUserId());
}
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
new file mode 100644
index 0000000..32f0698
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ondeviceintelligence.callbacks;
+
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback
+ * such that, in the case where the callback methods are not invoked, we do not have to wait for
+ * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in
+ * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
+ * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
+ * download will not complete and enabling faster cleanup.
+ */
+public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
+ private final IDownloadCallback callback;
+ private final Handler handler;
+ private final AndroidFuture future;
+ private final long idleTimeoutMs;
+
+ /**
+ * Constructor to create a ListenableDownloadCallback.
+ *
+ * @param callback callback to send download updates to caller.
+ * @param handler handler to schedule timeout runnable.
+ * @param future future to complete to signal the callback has reached a terminal state.
+ * @param idleTimeoutMs timeout within which download updates should be received.
+ */
+ public ListenableDownloadCallback(IDownloadCallback callback, Handler handler,
+ AndroidFuture future,
+ long idleTimeoutMs) {
+ this.callback = callback;
+ this.handler = handler;
+ this.future = future;
+ this.idleTimeoutMs = idleTimeoutMs;
+ handler.postDelayed(this,
+ idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked
+ }
+
+ @Override
+ public void onDownloadStarted(long bytesToDownload) throws RemoteException {
+ callback.onDownloadStarted(bytesToDownload);
+ handler.removeCallbacks(this);
+ handler.postDelayed(this, idleTimeoutMs);
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesDownloaded) throws RemoteException {
+ callback.onDownloadProgress(bytesDownloaded);
+ handler.removeCallbacks(this); // remove previously queued timeout tasks.
+ handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update.
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus,
+ String errorMessage, PersistableBundle errorParams) throws RemoteException {
+ callback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+ handler.removeCallbacks(this);
+ future.completeExceptionally(new TimeoutException());
+ }
+
+ @Override
+ public void onDownloadCompleted(
+ android.os.PersistableBundle downloadParams) throws RemoteException {
+ callback.onDownloadCompleted(downloadParams);
+ handler.removeCallbacks(this);
+ future.complete(null);
+ }
+
+ @Override
+ public void run() {
+ future.completeExceptionally(
+ new TimeoutException()); // complete the future as we haven't received updates
+ // for download progress.
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 9ba88aa..fe774aa 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -504,9 +504,12 @@
} else {
storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
}
- List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
- UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
- true /* onlyCoreApps */);
+ final List<String> deferPackages;
+ synchronized (mPm.mInstallLock) {
+ deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
+ UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
+ true /* onlyCoreApps */);
+ }
Future<?> prepareAppDataFuture = SystemServerInitThreadPool.submit(() -> {
TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync",
Trace.TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 19a0ba7..472f228 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -37,7 +37,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
@@ -505,6 +505,7 @@
// metadata file path for the new package.
if (oldPkgSetting != null) {
pkgSetting.setAppMetadataFilePath(null);
+ pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
}
// If the app metadata file path is not null then this is a system app with a preloaded app
// metadata file on the system image. Do not reset the path and source if this is the
@@ -523,7 +524,7 @@
}
} else if (Flags.aslInApkAppMetadataSource()) {
Map<String, PackageManager.Property> properties = pkg.getProperties();
- if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
// ASL file extraction is done in post-install
pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
@@ -985,13 +986,13 @@
}
void installPackagesTraced(List<InstallRequest> requests) {
- synchronized (mPm.mInstallLock) {
- try {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
- installPackagesLI(requests);
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
+ mPm.mInstallLock.lock();
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+ installPackagesLI(requests);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ mPm.mInstallLock.unlock();
}
}
@@ -2590,22 +2591,30 @@
final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
dexoptOptions, mContext);
if (performDexopt) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ // dexopt can take long, and ArtService doesn't require installd, so we release
+ // the lock here and re-acquire the lock after dexopt is finished.
+ mPm.mInstallLock.unlock();
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- // This mirrors logic from commitReconciledScanResultLocked, where the library files
- // needed for dexopt are assigned.
- PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
+ // This mirrors logic from commitReconciledScanResultLocked, where the library
+ // files needed for dexopt are assigned.
+ PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
- // Unfortunately, the updated system app flag is only tracked on this PackageSetting
- boolean isUpdatedSystemApp =
- installRequest.getScannedPackageSetting().isUpdatedSystemApp();
+ // Unfortunately, the updated system app flag is only tracked on this
+ // PackageSetting
+ boolean isUpdatedSystemApp =
+ installRequest.getScannedPackageSetting().isUpdatedSystemApp();
- realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
+ realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
- DexoptResult dexOptResult =
- DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions);
- installRequest.onDexoptFinished(dexOptResult);
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ } finally {
+ mPm.mInstallLock.lock();
+ }
}
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ae485ed..fda8535 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -626,7 +626,7 @@
// Lock for state used when installing and doing other long running
// operations. Methods that must be called with this lock held have
// the suffix "LI".
- final Object mInstallLock;
+ final PackageManagerTracedLock mInstallLock;
// ----------------------------------------------------------------
@@ -1692,8 +1692,8 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("create package manager");
- final PackageManagerTracedLock lock = new PackageManagerTracedLock();
- final Object installLock = new Object();
+ final PackageManagerTracedLock lock = new PackageManagerTracedLock("mLock");
+ final PackageManagerTracedLock installLock = new PackageManagerTracedLock("mInstallLock");
HandlerThread backgroundThread = new ServiceThread("PackageManagerBg",
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
@@ -4003,7 +4003,7 @@
final PackageMetrics.ComponentStateMetrics componentStateMetrics =
new PackageMetrics.ComponentStateMetrics(setting,
UserHandle.getUid(userId, packageSetting.getAppId()),
- packageSetting.getEnabled(userId));
+ packageSetting.getEnabled(userId), callingUid);
if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
callingPackage)) {
continue;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 83f3b16..ae2eaeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -86,7 +86,7 @@
private final Context mContext;
private final PackageManagerTracedLock mLock;
private final Installer mInstaller;
- private final Object mInstallLock;
+ private final PackageManagerTracedLock mInstallLock;
private final Handler mBackgroundHandler;
private final Executor mBackgroundExecutor;
private final List<ScanPartition> mSystemPartitions;
@@ -144,7 +144,7 @@
private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper;
PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
- Installer installer, Object installLock, PackageAbiHelper abiHelper,
+ Installer installer, PackageManagerTracedLock installLock, PackageAbiHelper abiHelper,
Handler backgroundHandler,
List<ScanPartition> systemPartitions,
Producer<ComponentResolver> componentResolverProducer,
@@ -254,7 +254,7 @@
return mAbiHelper;
}
- public Object getInstallLock() {
+ public PackageManagerTracedLock getInstallLock() {
return mInstallLock;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index b369f03..23ae983 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,7 +19,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
@@ -71,8 +71,12 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
+import android.os.CancellationSignal;
import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
@@ -93,6 +97,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Base64;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Printer;
@@ -147,11 +152,10 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
/**
* Class containing helper methods for the PackageManagerService.
@@ -1668,11 +1672,11 @@
return true;
}
Map<String, Property> properties = pkg.getProperties();
- if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
return false;
}
- Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH);
- if (!fileInAPkPathProperty.isString()) {
+ Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL);
+ if (!fileInApkProperty.isResourceId()) {
return false;
}
if (isSystem && !appMetadataFile.getParentFile().exists()) {
@@ -1684,28 +1688,46 @@
return false;
}
}
- String fileInApkPath = fileInAPkPathProperty.getString();
List<AndroidPackageSplit> splits = pkg.getSplits();
+ AssetManager.Builder builder = new AssetManager.Builder();
for (int i = 0; i < splits.size(); i++) {
- try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) {
- ZipEntry zipEntry = zipFile.getEntry(fileInApkPath);
- if (zipEntry != null
- && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) {
- try (InputStream in = zipFile.getInputStream(zipEntry)) {
- try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
- FileUtils.copy(in, out);
- Os.chmod(appMetadataFile.getAbsolutePath(),
- APP_METADATA_FILE_ACCESS_MODE);
- return true;
+ try {
+ builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath()));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath());
+ }
+ }
+ AssetManager assetManager = builder.build();
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ displayMetrics.setToDefaults();
+ Resources res = new Resources(assetManager, displayMetrics, null);
+ AtomicBoolean copyFailed = new AtomicBoolean(false);
+ try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) {
+ try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+ if (isSystem) {
+ FileUtils.copy(in, out);
+ } else {
+ long sizeLimit = getAppMetadataSizeLimit();
+ CancellationSignal signal = new CancellationSignal();
+ FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> {
+ if (progress > sizeLimit) {
+ copyFailed.set(true);
+ signal.cancel();
}
- }
+ });
}
- } catch (Exception e) {
- Slog.e(TAG, e.getMessage());
+ Os.chmod(appMetadataFile.getAbsolutePath(),
+ APP_METADATA_FILE_ACCESS_MODE);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, e.getMessage());
+ copyFailed.set(true);
+ } finally {
+ if (copyFailed.get()) {
appMetadataFile.delete();
}
}
- return false;
+ return !copyFailed.get();
}
public static void linkFilesToOldDirs(@NonNull Installer installer,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7a36f6d..0a8b2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -328,6 +328,8 @@
return runGetPrivappDenyPermissions();
case "get-oem-permissions":
return runGetOemPermissions();
+ case "get-signature-permission-allowlist":
+ return runGetSignaturePermissionAllowlist();
case "trim-caches":
return runTrimCaches();
case "create-user":
@@ -2920,6 +2922,54 @@
return 0;
}
+ private int runGetSignaturePermissionAllowlist() {
+ final var partition = getNextArg();
+ if (partition == null) {
+ getErrPrintWriter().println("Error: no partition specified.");
+ return 1;
+ }
+ final var permissionAllowlist =
+ SystemConfig.getInstance().getPermissionAllowlist();
+ final ArrayMap<String, ArrayMap<String, Boolean>> allowlist;
+ switch (partition) {
+ case "system":
+ allowlist = permissionAllowlist.getSignatureAppAllowlist();
+ break;
+ case "vendor":
+ allowlist = permissionAllowlist.getVendorSignatureAppAllowlist();
+ break;
+ case "product":
+ allowlist = permissionAllowlist.getProductSignatureAppAllowlist();
+ break;
+ case "system-ext":
+ allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown partition: " + partition);
+ return 1;
+ }
+ final var ipw = new IndentingPrintWriter(getOutPrintWriter(), " ");
+ final var allowlistSize = allowlist.size();
+ for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) {
+ final var packageName = allowlist.keyAt(allowlistIndex);
+ final var permissions = allowlist.valueAt(allowlistIndex);
+ ipw.print("Package: ");
+ ipw.println(packageName);
+ ipw.increaseIndent();
+ final var permissionsSize = permissions.size();
+ for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
+ final var permissionName = permissions.keyAt(permissionsIndex);
+ final var granted = permissions.valueAt(permissionsIndex);
+ if (granted) {
+ ipw.print("Permission: ");
+ ipw.println(permissionName);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+ return 0;
+ }
+
private int runTrimCaches() throws RemoteException {
String size = getNextArg();
if (size == null) {
@@ -4852,6 +4902,10 @@
pw.println(" get-oem-permissions TARGET-PACKAGE");
pw.println(" Prints all OEM permissions for a package.");
pw.println("");
+ pw.println(" get-signature-permission-allowlist PARTITION");
+ pw.println(" Prints the signature permission allowlist for a partition.");
+ pw.println(" PARTITION is one of system, vendor, product and system-ext");
+ pw.println("");
pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]");
pw.println(" Trim cache files to reach the given free space.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index 75e1803f..303b8b9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,6 +16,9 @@
package com.android.server.pm;
+import android.annotation.Nullable;
+import android.util.Slog;
+
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -23,4 +26,31 @@
* injection, similar to {@link ActivityManagerGlobalLock}.
*/
public class PackageManagerTracedLock extends ReentrantLock {
+ private static final String TAG = "PackageManagerTracedLock";
+ private static final boolean DEBUG = false;
+ @Nullable private final String mLockName;
+
+ public PackageManagerTracedLock(@Nullable String lockName) {
+ mLockName = lockName;
+ }
+
+ public PackageManagerTracedLock() {
+ this(null);
+ }
+
+ @Override
+ public void lock() {
+ super.lock();
+ if (DEBUG && mLockName != null) {
+ Slog.i(TAG, "locked " + mLockName);
+ }
+ }
+
+ @Override
+ public void unlock() {
+ super.unlock();
+ if (DEBUG && mLockName != null) {
+ Slog.i(TAG, "unlocked " + mLockName);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 20598f9..2081f73 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -358,6 +358,7 @@
public static class ComponentStateMetrics {
public int mUid;
+ public int mCallingUid;
public int mComponentOldState;
public int mComponentNewState;
public boolean mIsForWholeApp;
@@ -365,13 +366,14 @@
@Nullable private String mClassName;
ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
- int componentOldState) {
+ int componentOldState, int callingUid) {
mUid = uid;
mComponentOldState = componentOldState;
mComponentNewState = setting.getEnabledState();
mIsForWholeApp = !setting.isComponent();
mPackageName = setting.getPackageName();
mClassName = setting.getClassName();
+ mCallingUid = callingUid;
}
public boolean isSameComponent(ActivityInfo activityInfo) {
@@ -412,14 +414,15 @@
componentStateMetrics.mComponentOldState,
componentStateMetrics.mComponentNewState,
isLauncher,
- componentStateMetrics.mIsForWholeApp);
+ componentStateMetrics.mIsForWholeApp,
+ componentStateMetrics.mCallingUid);
}
}
private static void reportComponentStateChanged(int uid, int componentOldState,
- int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) {
FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
- uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid);
}
private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 41d6288..8d6d774 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2142,7 +2142,8 @@
ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
originalComponentName);
if (unflattenOriginalComponentName == null) {
- Slog.d(TAG, "Incorrect component name from the attributes");
+ Slog.wtf(TAG, "Incorrect component name: " + originalComponentName
+ + " from the attributes");
continue;
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 1d41401..ef32485 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -52,11 +52,11 @@
private static final String TAG = "UserDataPreparer";
private static final String XATTR_SERIAL = "user.serial";
- private final Object mInstallLock;
+ private final PackageManagerTracedLock mInstallLock;
private final Context mContext;
private final Installer mInstaller;
- UserDataPreparer(Installer installer, Object installLock, Context context) {
+ UserDataPreparer(Installer installer, PackageManagerTracedLock installLock, Context context) {
mInstallLock = installLock;
mContext = context;
mInstaller = installer;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b1976cd..ebdca5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -600,8 +600,11 @@
public void onReceive(Context context, Intent intent) {
if (isAutoLockForPrivateSpaceEnabled()) {
if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ Slog.d(LOG_TAG, "SCREEN_OFF broadcast received");
maybeScheduleMessageToAutoLockPrivateSpace();
} else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+ Slog.d(LOG_TAG, "SCREEN_ON broadcast received, "
+ + "removing queued message to auto-lock private space");
// Remove any queued messages since the device is interactive again
mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
}
@@ -619,6 +622,8 @@
getMainUserIdUnchecked());
if (privateSpaceAutoLockPreference
!= Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ Slogf.d(LOG_TAG, "Not scheduling auto-lock on inactivity,"
+ + "preference is set to %d", privateSpaceAutoLockPreference);
return;
}
int privateProfileUserId = getPrivateProfileUserId();
@@ -632,6 +637,7 @@
@VisibleForTesting
void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
long delayInMillis) {
+ Slog.i(LOG_TAG, "Scheduling auto-lock message");
mHandler.postDelayed(() -> {
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
if (powerManager != null && !powerManager.isInteractive()) {
@@ -1060,8 +1066,6 @@
if (isAutoLockingPrivateSpaceOnRestartsEnabled()) {
autoLockPrivateSpace();
}
-
- markEphemeralUsersForRemoval();
}
private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
@@ -1098,21 +1102,6 @@
}
}
- /** Marks all ephemeral users as slated for deletion. **/
- private void markEphemeralUsersForRemoval() {
- synchronized (mUsersLock) {
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- final UserInfo ui = mUsers.valueAt(i).info;
- if (ui.isEphemeral() && !ui.preCreated && ui.id != UserHandle.USER_SYSTEM) {
- addRemovingUserIdLocked(ui.id);
- ui.partial = true;
- ui.flags |= UserInfo.FLAG_DISABLED;
- }
- }
- }
- }
-
/* Prunes out any partially created or partially removed users. */
private void cleanupPartialUsers() {
ArrayList<UserInfo> partials = new ArrayList<>();
@@ -1935,16 +1924,20 @@
private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
- // TODO (b/308121702) It may be brittle to rely on user states to check profile state
- int state;
- synchronized (mUserStates) {
- state = mUserStates.get(userId, UserState.STATE_NONE);
- }
- if (state != UserState.STATE_NONE) {
- Slog.i(LOG_TAG,
- "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
- + " is still alive.");
- return;
+ if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles()
+ || getUserInfo(userId).isManagedProfile()) {
+ // TODO (b/308121702) It may be brittle to rely on user states to check managed
+ // profile state
+ int state;
+ synchronized (mUserStates) {
+ state = mUserStates.get(userId, UserState.STATE_NONE);
+ }
+ if (state != UserState.STATE_NONE) {
+ Slog.i(LOG_TAG,
+ "showConfirmCredentialToDisableQuietMode() called too early, managed "
+ + "user " + userId + " is still alive.");
+ return;
+ }
}
}
// otherwise, we show a profile challenge to trigger decryption of the user
@@ -4223,6 +4216,13 @@
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
}
+ if (userData.info.isEphemeral() && !userData.info.preCreated
+ && userData.info.id != UserHandle.USER_SYSTEM) {
+ // Mark ephemeral user as slated for deletion.
+ addRemovingUserIdLocked(userData.info.id);
+ userData.info.partial = true;
+ userData.info.flags |= UserInfo.FLAG_DISABLED;
+ }
}
}
} else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 5aad570..884c26c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,12 +19,9 @@
import android.annotation.NonNull;
import android.os.BatteryConsumer;
-import com.android.internal.os.PowerStats;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -206,25 +203,9 @@
return mPowerComponents;
}
- private static final PowerStatsProcessor NO_OP_PROCESSOR =
- new PowerStatsProcessor() {
- @Override
- void finish(PowerComponentAggregatedPowerStats stats) {
- }
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return Arrays.toString(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- return descriptor.getStateLabel(key) + " " + Arrays.toString(stats);
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return Arrays.toString(stats);
- }
- };
+ private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() {
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats) {
+ }
+ };
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 49c4000..9a41551 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -463,11 +463,17 @@
public static class BatteryStatsConfig {
static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
- static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD =
- TimeUnit.HOURS.toMillis(1);
private final int mFlags;
- private SparseLongArray mPowerStatsThrottlePeriods;
+ private final Long mDefaultPowerStatsThrottlePeriod;
+ private final Map<String, Long> mPowerStatsThrottlePeriods;
+
+ @VisibleForTesting
+ public BatteryStatsConfig() {
+ mFlags = 0;
+ mDefaultPowerStatsThrottlePeriod = 0L;
+ mPowerStatsThrottlePeriods = Map.of();
+ }
private BatteryStatsConfig(Builder builder) {
int flags = 0;
@@ -478,6 +484,7 @@
flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
mFlags = flags;
+ mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod;
mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
}
@@ -485,7 +492,7 @@
* Returns whether a BatteryStats reset should occur on unplug when the battery level is
* high.
*/
- boolean shouldResetOnUnplugHighBatteryLevel() {
+ public boolean shouldResetOnUnplugHighBatteryLevel() {
return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG)
== RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
}
@@ -494,14 +501,18 @@
* Returns whether a BatteryStats reset should occur on unplug if the battery charge a
* significant amount since it has been plugged in.
*/
- boolean shouldResetOnUnplugAfterSignificantCharge() {
+ public boolean shouldResetOnUnplugAfterSignificantCharge() {
return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG)
== RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
- long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) {
- return mPowerStatsThrottlePeriods.get(powerComponent,
- DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD);
+ /**
+ * Returns the minimum amount of time (in millis) to wait between passes
+ * of power stats collection for the specified power component.
+ */
+ public long getPowerStatsThrottlePeriod(String powerComponentName) {
+ return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName,
+ mDefaultPowerStatsThrottlePeriod);
}
/**
@@ -510,18 +521,19 @@
public static class Builder {
private boolean mResetOnUnplugHighBatteryLevel;
private boolean mResetOnUnplugAfterSignificantCharge;
- private SparseLongArray mPowerStatsThrottlePeriods;
+ public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
+ TimeUnit.HOURS.toMillis(1);
+ public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
+ TimeUnit.MINUTES.toMillis(1);
+ private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD;
+ private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>();
public Builder() {
mResetOnUnplugHighBatteryLevel = true;
mResetOnUnplugAfterSignificantCharge = true;
- mPowerStatsThrottlePeriods = new SparseLongArray();
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
- TimeUnit.MINUTES.toMillis(1));
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- TimeUnit.HOURS.toMillis(1));
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
- TimeUnit.HOURS.toMillis(1));
+ setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU),
+ DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU);
}
/**
@@ -553,9 +565,18 @@
* Sets the minimum amount of time (in millis) to wait between passes
* of power stats collection for the specified power component.
*/
- public Builder setPowerStatsThrottlePeriodMillis(
- @BatteryConsumer.PowerComponent int powerComponent, long periodMs) {
- mPowerStatsThrottlePeriods.put(powerComponent, periodMs);
+ public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName,
+ long periodMs) {
+ mPowerStatsThrottlePeriods.put(powerComponentName, periodMs);
+ return this;
+ }
+
+ /**
+ * Sets the minimum amount of time (in millis) to wait between passes
+ * of power stats collection for any components not configured explicitly.
+ */
+ public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) {
+ mDefaultPowerStatsThrottlePeriod = periodMs;
return this;
}
}
@@ -1586,8 +1607,7 @@
protected final Constants mConstants;
@VisibleForTesting
- @GuardedBy("this")
- protected BatteryStatsConfig mBatteryStatsConfig;
+ protected final BatteryStatsConfig mBatteryStatsConfig;
@GuardedBy("this")
private AlarmManager mAlarmManager = null;
@@ -1933,6 +1953,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName);
+ }
+
+ @Override
public PowerStatsUidResolver getUidResolver() {
return mPowerStatsUidResolver;
}
@@ -11167,19 +11192,14 @@
mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
mClock, mMonotonicClock, traceDelegate, eventLogger);
- mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector,
- mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_CPU));
+ mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
- mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+ mPowerStatsCollectorInjector);
mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
- mWifiPowerStatsCollector = new WifiPowerStatsCollector(
- mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_WIFI));
+ mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector);
mWifiPowerStatsCollector.addConsumer(this::recordPowerStats);
mStartCount++;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index f53a1b0..b5ef67b 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -59,6 +59,7 @@
KernelCpuStatsReader getKernelCpuStatsReader();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
default int getDefaultCpuPowerBrackets() {
return DEFAULT_CPU_POWER_BRACKETS;
@@ -94,9 +95,11 @@
private int mLastVoltageMv;
private long[] mLastConsumedEnergyUws;
- public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
- injector.getClock());
+ CpuPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU)),
+ injector.getUidResolver(), injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
index 1bcb2c4..2a02bd0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
@@ -44,7 +44,7 @@
* Declare that the stats array has a section capturing CPU time per scaling step
*/
public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
- mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
+ mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps");
mDeviceCpuTimeByScalingStepCount = scalingStepCount;
}
@@ -72,7 +72,7 @@
* Declare that the stats array has a section capturing CPU time in each cluster
*/
public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
- mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
+ mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters");
mDeviceCpuTimeByClusterCount = clusterCount;
}
@@ -102,7 +102,7 @@
public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
updatePowerBracketCount();
- mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
+ mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time");
}
private void updatePowerBracketCount() {
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
index c34b8a8..57b7259 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
@@ -16,7 +16,6 @@
package com.android.server.power.stats;
-import android.os.BatteryStats;
import android.util.ArraySet;
import android.util.Log;
@@ -487,64 +486,4 @@
stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
}
}
-
- @Override
- public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
- sb.append("steps: [");
- for (int step = 0; step < cpuScalingStepCount; step++) {
- if (step != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
- }
- int clusterCount = mStatsLayout.getCpuClusterCount();
- sb.append("] clusters: [");
- for (int cluster = 0; cluster < clusterCount; cluster++) {
- if (cluster != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
- }
- sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats));
- int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
- if (energyConsumerCount > 0) {
- sb.append(" energy: [");
- for (int i = 0; i < energyConsumerCount; i++) {
- if (i != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getConsumedEnergy(stats, i));
- }
- sb.append("]");
- }
- sb.append(" power: ").append(
- BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
- return sb.toString();
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- sb.append("[");
- int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
- for (int bracket = 0; bracket < powerBracketCount; bracket++) {
- if (bracket != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
- }
- sb.append("] power: ").append(
- BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
- return sb.toString();
- }
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 7bc6817..a96e01b 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -73,6 +73,7 @@
Handler getHandler();
Clock getClock();
PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
PackageManager getPackageManager();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
@@ -104,8 +105,11 @@
private long mLastCallDuration;
private long mLastScanDuration;
- public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ MobileRadioPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
+ injector.getUidResolver(),
injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
index 81d7c2f..07d78f8 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
@@ -64,29 +64,30 @@
}
void addDeviceMobileActivity() {
- mDeviceSleepTimePosition = addDeviceSection(1);
- mDeviceIdleTimePosition = addDeviceSection(1);
- mDeviceScanTimePosition = addDeviceSection(1);
- mDeviceCallTimePosition = addDeviceSection(1);
+ mDeviceSleepTimePosition = addDeviceSection(1, "sleep");
+ mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+ mDeviceScanTimePosition = addDeviceSection(1, "scan");
+ mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL);
}
void addStateStats() {
- mStateRxTimePosition = addStateSection(1);
+ mStateRxTimePosition = addStateSection(1, "rx");
mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels();
- mStateTxTimesPosition = addStateSection(mStateTxTimesCount);
+ mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx");
}
void addUidNetworkStats() {
- mUidRxBytesPosition = addUidSection(1);
- mUidTxBytesPosition = addUidSection(1);
- mUidRxPacketsPosition = addUidSection(1);
- mUidTxPacketsPosition = addUidSection(1);
+ mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+ mUidRxBytesPosition = addUidSection(1, "rx-B");
+ mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+ mUidTxBytesPosition = addUidSection(1, "tx-B");
}
@Override
public void addDeviceSectionPowerEstimate() {
super.addDeviceSectionPowerEstimate();
- mDeviceCallPowerPosition = addDeviceSection(1);
+ // Printed as part of the PhoneCallPowerStatsProcessor
+ mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN);
}
public void setDeviceSleepTime(long[] stats, long durationMillis) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
index c97c64b..eebed2f 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
@@ -398,37 +398,4 @@
}
}
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "idle: " + mStatsLayout.getDeviceIdleTime(stats)
- + " sleep: " + mStatsLayout.getDeviceSleepTime(stats)
- + " scan: " + mStatsLayout.getDeviceScanTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- sb.append(descriptor.getStateLabel(key));
- sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats));
- sb.append(" tx: ");
- for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
- if (txLevel != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getStateTxTime(stats, txLevel));
- }
- return sb.toString();
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "rx: " + mStatsLayout.getUidRxPackets(stats)
- + " tx: " + mStatsLayout.getUidTxPackets(stats)
- + " power: " + mStatsLayout.getUidPowerEstimate(stats);
- }
}
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
index 6c4a2b6..a822281 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -28,10 +28,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.Arrays;
import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Maintains multidimensional multi-state stats. States could be something like on-battery (0,1),
@@ -287,6 +285,14 @@
mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
}
+ public int getDimensionCount() {
+ return mFactory.mDimensionCount;
+ }
+
+ public States[] getStates() {
+ return mFactory.mStates;
+ }
+
/**
* Copies time-in-state and timestamps from the supplied prototype. Does not
* copy accumulated counts.
@@ -343,11 +349,6 @@
mTracking = false;
}
- @Override
- public String toString() {
- return mCounter.toString();
- }
-
/**
* Stores contents in an XML doc.
*/
@@ -451,10 +452,9 @@
return true;
}
- /**
- * Prints the accumulated stats, one line of every combination of states that has data.
- */
- public void dump(PrintWriter pw, Function<long[], String> statsFormatter) {
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
long[] values = new long[mCounter.getArrayLength()];
States.forEachTrackedStateCombination(mFactory.mStates, states -> {
mCounter.getCounts(values, mFactory.getSerialState(states));
@@ -469,18 +469,24 @@
return;
}
- StringBuilder sb = new StringBuilder();
+ if (!sb.isEmpty()) {
+ sb.append("\n");
+ }
+
+ sb.append("(");
+ boolean first = true;
for (int i = 0; i < states.length; i++) {
if (mFactory.mStates[i].mTracked) {
- if (sb.length() != 0) {
+ if (!first) {
sb.append(" ");
}
+ first = false;
sb.append(mFactory.mStates[i].mLabels[states[i]]);
}
}
- sb.append(" ");
- sb.append(statsFormatter.apply(values));
- pw.println(sb);
+ sb.append(") ");
+ sb.append(Arrays.toString(values));
});
+ return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
index 62b653f..5c545fd 100644
--- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
@@ -76,21 +76,4 @@
stats.setDeviceStats(states, mTmpDeviceStats);
});
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return "power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- // Unsupported for this power component
- return null;
- }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 6d58307..0528733 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -436,36 +436,76 @@
void dumpDevice(IndentingPrintWriter ipw) {
if (mDeviceStats != null) {
- ipw.println(mPowerStatsDescriptor.name);
- ipw.increaseIndent();
- mDeviceStats.dump(ipw, stats ->
- mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
- ipw.decreaseIndent();
+ dumpMultiStateStats(ipw, mDeviceStats, mPowerStatsDescriptor.name, null,
+ mPowerStatsDescriptor.getDeviceStatsFormatter());
}
if (mStateStats.size() != 0) {
ipw.increaseIndent();
- ipw.println(mPowerStatsDescriptor.name + " states");
- ipw.increaseIndent();
+ String header = mPowerStatsDescriptor.name + " states";
+ PowerStats.PowerStatsFormatter formatter =
+ mPowerStatsDescriptor.getStateStatsFormatter();
for (int i = 0; i < mStateStats.size(); i++) {
int key = mStateStats.keyAt(i);
+ String stateLabel = mPowerStatsDescriptor.getStateLabel(key);
MultiStateStats stateStats = mStateStats.valueAt(i);
- stateStats.dump(ipw, stats ->
- mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key,
- stats));
+ dumpMultiStateStats(ipw, stateStats, header, stateLabel, formatter);
}
ipw.decreaseIndent();
- ipw.decreaseIndent();
}
}
void dumpUid(IndentingPrintWriter ipw, int uid) {
UidStats uidStats = mUidStats.get(uid);
if (uidStats != null && uidStats.stats != null) {
- ipw.println(mPowerStatsDescriptor.name);
- ipw.increaseIndent();
- uidStats.stats.dump(ipw, stats ->
- mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats));
+ dumpMultiStateStats(ipw, uidStats.stats, mPowerStatsDescriptor.name, null,
+ mPowerStatsDescriptor.getUidStatsFormatter());
+ }
+ }
+
+ private void dumpMultiStateStats(IndentingPrintWriter ipw, MultiStateStats stats,
+ String header, String additionalLabel,
+ PowerStats.PowerStatsFormatter statsFormatter) {
+ boolean[] firstLine = new boolean[]{true};
+ long[] values = new long[stats.getDimensionCount()];
+ MultiStateStats.States[] stateInfo = stats.getStates();
+ MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> {
+ stats.getStats(values, states);
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ if (firstLine[0]) {
+ ipw.println(header);
+ ipw.increaseIndent();
+ }
+ firstLine[0] = false;
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ boolean first = true;
+ for (int i = 0; i < states.length; i++) {
+ if (stateInfo[i].isTracked()) {
+ if (!first) {
+ sb.append(" ");
+ }
+ first = false;
+ sb.append(stateInfo[i].getLabels()[states[i]]);
+ }
+ }
+ if (additionalLabel != null) {
+ sb.append(" ").append(additionalLabel);
+ }
+ sb.append(") ").append(statsFormatter.format(values));
+ ipw.println(sb);
+ });
+ if (!firstLine[0]) {
ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
index aa96409..58efd94 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
@@ -33,13 +33,20 @@
private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
private static final String EXTRA_UID_POWER_POSITION = "up";
- protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
protected static final int UNSUPPORTED = -1;
+ protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+ protected static final int FLAG_OPTIONAL = 1;
+ protected static final int FLAG_HIDDEN = 2;
+ protected static final int FLAG_FORMAT_AS_POWER = 4;
private int mDeviceStatsArrayLength;
private int mStateStatsArrayLength;
private int mUidStatsArrayLength;
+ private StringBuilder mDeviceFormat = new StringBuilder();
+ private StringBuilder mStateFormat = new StringBuilder();
+ private StringBuilder mUidFormat = new StringBuilder();
+
protected int mDeviceDurationPosition = UNSUPPORTED;
private int mDeviceEnergyConsumerPosition;
private int mDeviceEnergyConsumerCount;
@@ -65,29 +72,71 @@
return mUidStatsArrayLength;
}
- protected int addDeviceSection(int length) {
+ /**
+ * @param label should not contain either spaces or colons
+ */
+ private void appendFormat(StringBuilder sb, int position, int length, String label,
+ int flags) {
+ if ((flags & FLAG_HIDDEN) != 0) {
+ return;
+ }
+
+ if (!sb.isEmpty()) {
+ sb.append(' ');
+ }
+
+ sb.append(label).append(':');
+ sb.append(position);
+ if (length != 1) {
+ sb.append('[').append(length).append(']');
+ }
+ if ((flags & FLAG_FORMAT_AS_POWER) != 0) {
+ sb.append('p');
+ }
+ if ((flags & FLAG_OPTIONAL) != 0) {
+ sb.append('?');
+ }
+ }
+
+ protected int addDeviceSection(int length, String label, int flags) {
int position = mDeviceStatsArrayLength;
mDeviceStatsArrayLength += length;
+ appendFormat(mDeviceFormat, position, length, label, flags);
return position;
}
- protected int addStateSection(int length) {
+ protected int addDeviceSection(int length, String label) {
+ return addDeviceSection(length, label, 0);
+ }
+
+ protected int addStateSection(int length, String label, int flags) {
int position = mStateStatsArrayLength;
mStateStatsArrayLength += length;
+ appendFormat(mStateFormat, position, length, label, flags);
return position;
}
- protected int addUidSection(int length) {
+ protected int addStateSection(int length, String label) {
+ return addStateSection(length, label, 0);
+ }
+
+
+ protected int addUidSection(int length, String label, int flags) {
int position = mUidStatsArrayLength;
mUidStatsArrayLength += length;
+ appendFormat(mUidFormat, position, length, label, flags);
return position;
}
+ protected int addUidSection(int length, String label) {
+ return addUidSection(length, label, 0);
+ }
+
/**
* Declare that the stats array has a section capturing usage duration
*/
public void addDeviceSectionUsageDuration() {
- mDeviceDurationPosition = addDeviceSection(1);
+ mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL);
}
/**
@@ -109,7 +158,7 @@
* PowerStatsService.
*/
public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
- mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
+ mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy");
mDeviceEnergyConsumerCount = energyConsumerCount;
}
@@ -137,7 +186,8 @@
* Declare that the stats array has a section capturing a power estimate
*/
public void addDeviceSectionPowerEstimate() {
- mDevicePowerEstimatePosition = addDeviceSection(1);
+ mDevicePowerEstimatePosition = addDeviceSection(1, "power",
+ FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
}
/**
@@ -159,7 +209,7 @@
* Declare that the UID stats array has a section capturing a power estimate
*/
public void addUidSectionPowerEstimate() {
- mUidPowerEstimatePosition = addUidSection(1);
+ mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
}
/**
@@ -195,6 +245,9 @@
mDeviceEnergyConsumerCount);
extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+ extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
+ extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
+ extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
}
/**
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 0d5c542..2fd0b9a 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.util.Log;
-import com.android.internal.os.PowerStats;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -47,12 +45,6 @@
abstract void finish(PowerComponentAggregatedPowerStats stats);
- abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
- abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats);
-
- abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
protected static class PowerEstimationPlan {
private final AggregatedPowerStatsConfig.PowerComponent mConfig;
public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 6321053..bd04199 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -56,6 +56,7 @@
Handler getHandler();
Clock getClock();
PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
PackageManager getPackageManager();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
@@ -92,9 +93,11 @@
private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
private long mLastWifiActiveDuration;
- public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
- injector.getClock());
+ WifiPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_WIFI)),
+ injector.getUidResolver(), injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
index 0fa6ec6..e2e8226 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
@@ -65,28 +65,28 @@
mPowerReportingSupported = powerReportingSupported;
if (mPowerReportingSupported) {
mDeviceActiveTimePosition = UNSPECIFIED;
- mDeviceRxTimePosition = addDeviceSection(1);
- mDeviceTxTimePosition = addDeviceSection(1);
- mDeviceIdleTimePosition = addDeviceSection(1);
- mDeviceScanTimePosition = addDeviceSection(1);
+ mDeviceRxTimePosition = addDeviceSection(1, "rx");
+ mDeviceTxTimePosition = addDeviceSection(1, "tx");
+ mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+ mDeviceScanTimePosition = addDeviceSection(1, "scan");
} else {
- mDeviceActiveTimePosition = addDeviceSection(1);
+ mDeviceActiveTimePosition = addDeviceSection(1, "rx-tx");
mDeviceRxTimePosition = UNSPECIFIED;
mDeviceTxTimePosition = UNSPECIFIED;
mDeviceIdleTimePosition = UNSPECIFIED;
mDeviceScanTimePosition = UNSPECIFIED;
}
- mDeviceBasicScanTimePosition = addDeviceSection(1);
- mDeviceBatchedScanTimePosition = addDeviceSection(1);
+ mDeviceBasicScanTimePosition = addDeviceSection(1, "basic-scan", FLAG_OPTIONAL);
+ mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL);
}
void addUidNetworkStats() {
- mUidRxBytesPosition = addUidSection(1);
- mUidTxBytesPosition = addUidSection(1);
- mUidRxPacketsPosition = addUidSection(1);
- mUidTxPacketsPosition = addUidSection(1);
- mUidScanTimePosition = addUidSection(1);
- mUidBatchScanTimePosition = addUidSection(1);
+ mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+ mUidRxBytesPosition = addUidSection(1, "rx-B");
+ mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+ mUidTxBytesPosition = addUidSection(1, "tx-B");
+ mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL);
+ mUidBatchScanTimePosition = addUidSection(1, "batched-scan", FLAG_OPTIONAL);
}
public boolean isPowerReportingSupported() {
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
index 5e9cc40..a4a2e18 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
@@ -389,37 +389,4 @@
}
}
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- if (mHasWifiPowerController) {
- return "rx: " + mStatsLayout.getDeviceRxTime(stats)
- + " tx: " + mStatsLayout.getDeviceTxTime(stats)
- + " scan: " + mStatsLayout.getDeviceScanTime(stats)
- + " idle: " + mStatsLayout.getDeviceIdleTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- } else {
- return "active: " + mStatsLayout.getDeviceActiveTime(stats)
- + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats)
- + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "rx: " + mStatsLayout.getUidRxPackets(stats)
- + " tx: " + mStatsLayout.getUidTxPackets(stats)
- + " scan: " + mStatsLayout.getUidScanTime(stats)
- + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats)
- + " power: " + mStatsLayout.getUidPowerEstimate(stats);
- }
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e3e478d..c1b825b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -49,10 +49,13 @@
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
@@ -61,7 +64,6 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
-import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -431,12 +433,6 @@
public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
addMobileBytesTransferByProcStatePuller();
- /**
- * Whether or not to enable the mNetworkStatsManager initialization order fix
- */
- private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
- statsPullNetworkStatsManagerInitOrderFix();
-
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -799,7 +795,7 @@
case FrameworkStatsLog.KEYSTORE2_CRASH_STATS:
return pullKeystoreAtoms(atomTag, data);
case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS:
- return pullAccessibilityShortcutStatsLocked(atomTag, data);
+ return pullAccessibilityShortcutStatsLocked(data);
case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS:
return pullAccessibilityFloatingMenuStatsLocked(atomTag, data);
case FrameworkStatsLog.MEDIA_CAPABILITIES:
@@ -840,9 +836,7 @@
registerEventListeners();
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
- initNetworkStatsManager();
- }
+ initNetworkStatsManager();
BackgroundThread.getHandler().post(() -> {
// Network stats related pullers can only be initialized after service is ready.
initAndRegisterNetworkStatsPullers();
@@ -863,9 +857,6 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
- if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
- initNetworkStatsManager();
- }
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1047,10 +1038,8 @@
*/
@NonNull
private NetworkStatsManager getNetworkStatsManager() {
- if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
- if (mNetworkStatsManager == null) {
- throw new IllegalStateException("NetworkStatsManager is not ready");
- }
+ if (mNetworkStatsManager == null) {
+ throw new IllegalStateException("NetworkStatsManager is not ready");
}
return mNetworkStatsManager;
}
@@ -4774,7 +4763,10 @@
}
}
- int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ /**
+ * Pulls ACCESSIBILITY_SHORTCUT_STATS atom
+ */
+ int pullAccessibilityShortcutStatsLocked(List<StatsEvent> pulledData) {
UserManager userManager = mContext.getSystemService(UserManager.class);
if (userManager == null) {
return StatsManager.PULL_SKIP;
@@ -4782,10 +4774,6 @@
final long token = Binder.clearCallingIdentity();
try {
final ContentResolver resolver = mContext.getContentResolver();
- final int hardware_shortcut_type =
- FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
- final int triple_tap_shortcut =
- FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
for (UserInfo userInfo : userManager.getUsers()) {
final int userId = userInfo.getUserHandle().getIdentifier();
@@ -4803,15 +4791,22 @@
final int hardware_shortcut_service_num = countAccessibilityServices(
hardware_shortcut_list);
+ final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId);
+ final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list);
+
// only allow magnification to use it for now
final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId);
-
- pulledData.add(
- FrameworkStatsLog.buildStatsEvent(atomTag,
- software_shortcut_type, software_shortcut_service_num,
- hardware_shortcut_type, hardware_shortcut_service_num,
- triple_tap_shortcut, triple_tap_service_num));
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS,
+ software_shortcut_type, software_shortcut_service_num,
+ ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY,
+ hardware_shortcut_service_num,
+ ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP,
+ triple_tap_service_num,
+ ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS,
+ qs_shortcut_enabled));
}
}
} catch (RuntimeException e) {
@@ -5150,16 +5145,19 @@
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver,
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId);
final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver,
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1;
final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list);
final boolean hardware_shortcut_enabled =
hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list);
+ final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list);
final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1;
return software_shortcut_enabled || hardware_shortcut_enabled
- || triple_tap_shortcut_enabled;
+ || triple_tap_shortcut_enabled || qs_shortcut_enabled;
}
private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) {
@@ -5176,13 +5174,13 @@
private int convertToAccessibilityShortcutType(int shortcutType) {
switch (shortcutType) {
case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR:
- return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+ return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON;
case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU:
- return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+ return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU;
case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE:
- return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+ return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE;
default:
- return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+ return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
}
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index c479c6d..6faa273 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -8,11 +8,3 @@
bug: "309512867"
is_fixed_read_only: true
}
-
-flag {
- name: "stats_pull_network_stats_manager_init_order_fix"
- namespace: "statsd"
- description: "Fix the mNetworkStatsManager initialization order"
- bug: "331989853"
- is_fixed_read_only: true
-}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index cdd456c..4264e91 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -950,7 +950,7 @@
if (mBar != null) {
try {
- mBar.togglePanel();
+ mBar.toggleNotificationsPanel();
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index f6afc52..3393d3e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,7 +150,11 @@
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+ if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
+ onWindowInfosChangedInternal(windowHandles, displayInfos);
+ } else {
+ mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+ }
}
private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 21e4c96..76e7f53 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -339,7 +339,6 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -2123,14 +2122,14 @@
if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
// When the stable configuration is the default behavior, override for the legacy apps
// without forward override flag.
- mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+ mResolveConfigHint.mUseOverrideInsetsForConfig =
!info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
&& !info.isChangeEnabled(
OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
} else {
// When the stable configuration is not the default behavior, forward overriding the
// listed apps.
- mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+ mResolveConfigHint.mUseOverrideInsetsForConfig =
info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
}
@@ -3272,8 +3271,12 @@
mOccludesParent = occludesParent;
setMainWindowOpaque(occludesParent);
- if (changed && task != null && !occludesParent) {
- getRootTask().convertActivityToTranslucent(this);
+ if (changed && task != null) {
+ if (!occludesParent) {
+ getRootTask().convertActivityToTranslucent(this);
+ } else {
+ getRootTask().convertActivityFromTranslucent(this);
+ }
}
// Always ensure visibility if this activity doesn't occlude parent, so the
// {@link #returningOptions} of the activity under this one can be applied in
@@ -4266,6 +4269,12 @@
getTaskFragment().cleanUpActivityReferences(this);
clearLastParentBeforePip();
+ // Abort and reset state if the scence transition is playing.
+ final Task rootTask = getRootTask();
+ if (rootTask != null) {
+ rootTask.abortTranslucentActivityWaiting(this);
+ }
+
// Clean up the splash screen if it was still displayed.
cleanUpSplashScreen();
@@ -5672,6 +5681,8 @@
} else if (mTransitionController.inFinishingTransition(this)) {
mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
}
+ } else {
+ mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
}
return;
}
@@ -6519,8 +6530,8 @@
// and the token could be null.
return;
}
- if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
- r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+ if (r.mDisplayContent.mActivityRefresher != null) {
+ r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
}
}
@@ -8480,7 +8491,7 @@
mCompatDisplayInsets =
new CompatDisplayInsets(
mDisplayContent, this, letterboxedContainerBounds,
- mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+ mResolveConfigHint.mUseOverrideInsetsForConfig);
}
private void clearSizeCompatModeAttributes() {
@@ -8560,8 +8571,6 @@
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
- applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
-
// Bubble activities should always fill their parent and should not be letterboxed.
final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
&& (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -8661,6 +8670,8 @@
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
logAppCompatState();
}
@@ -8679,14 +8690,13 @@
if (mDisplayContent == null) {
return;
}
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds
- || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
- || rotation == ROTATION_UNDEFINED) {
+ if (!mResolveConfigHint.mUseOverrideInsetsForConfig
+ || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
+ || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8703,53 +8713,7 @@
}
// Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
- : mDisplayContent.mBaseDisplayWidth;
- final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
- : mDisplayContent.mBaseDisplayHeight;
- final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(nonDecorInsets);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- inOutConfig.screenWidthDp = overrideScreenWidthDp;
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- inOutConfig.screenHeightDp = overrideScreenHeightDp;
- }
- if (inOutConfig.smallestScreenWidthDp
- == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
- mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
- mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
+ computeConfigByResolveHint(inOutConfig, newParentConfiguration);
}
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@@ -9005,7 +8969,7 @@
if (mDisplayContent == null) {
return true;
}
- if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) {
+ if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
// No insets should be considered any more.
return true;
}
@@ -9024,7 +8988,7 @@
final Task task = getTask();
task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
- mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+ mResolveConfigHint.mUseOverrideInsetsForConfig);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -9081,7 +9045,7 @@
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
- final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+ final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
? stableBoundsOrientation : newParentConfig.orientation;
// If the activity requires a different orientation (either by override or activityInfo),
@@ -9106,7 +9070,7 @@
return;
}
- final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+ final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForConfig
? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
@@ -10032,7 +9996,7 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
}
- notifyDisplayCompatPolicyAboutConfigurationChange(
+ notifyActivityRefresherAboutConfigurationChange(
mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
@@ -10099,18 +10063,18 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
}
- notifyDisplayCompatPolicyAboutConfigurationChange(
+ notifyActivityRefresherAboutConfigurationChange(
mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
- private void notifyDisplayCompatPolicyAboutConfigurationChange(
+ private void notifyActivityRefresherAboutConfigurationChange(
Configuration newConfig, Configuration lastReportedConfig) {
- if (mDisplayContent.mDisplayRotationCompatPolicy == null
+ if (mDisplayContent.mActivityRefresher == null
|| !shouldBeResumed(/* activeActivity */ null)) {
return;
}
- mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+ mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
this, newConfig, lastReportedConfig);
}
@@ -11111,7 +11075,7 @@
* Otherwise, return the creation time of the top window.
*/
long getLastWindowCreateTime() {
- final WindowState window = getWindow(win -> true);
+ final WindowState window = getWindow(alwaysTruePredicate());
return window != null && window.mAttrs.type != TYPE_BASE_APPLICATION
? window.getCreateTime()
: createTime;
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
new file mode 100644
index 0000000..23a9708
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
+ *
+ * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
+ * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
+ * through either stop or pause and then resume, based on the global config and per-app override.
+ */
+class ActivityRefresher {
+ // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+ // client process may not always report the event back to the server, such as process is
+ // crashed or got killed.
+ private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
+
+ @NonNull private final WindowManagerService mWmService;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
+
+ ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
+ mWmService = wmService;
+ mHandler = handler;
+ }
+
+ void addEvaluator(@NonNull Evaluator evaluator) {
+ mEvaluators.add(evaluator);
+ }
+
+ void removeEvaluator(@NonNull Evaluator evaluator) {
+ mEvaluators.remove(evaluator);
+ }
+
+ /**
+ * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ */
+ void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+ if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+ return;
+ }
+
+ final boolean cycleThroughStop =
+ mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled()
+ && !activity.mLetterboxUiController
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ activity.mLetterboxUiController.setIsRefreshRequested(true);
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Refreshing activity for freeform camera compatibility treatment, "
+ + "activityRecord=%s", activity);
+ final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+ activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+ activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ try {
+ activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+ activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+ mHandler.postDelayed(() -> {
+ synchronized (mWmService.mGlobalLock) {
+ onActivityRefreshed(activity);
+ }
+ }, REFRESH_CALLBACK_TIMEOUT_MS);
+ } catch (RemoteException e) {
+ activity.mLetterboxUiController.setIsRefreshRequested(false);
+ }
+ }
+
+ boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
+ return activity.mLetterboxUiController.isRefreshRequested();
+ }
+
+ void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
+ // state?
+ activity.mLetterboxUiController.setIsRefreshRequested(false);
+ }
+
+ private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+ return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
+ ((Evaluator) evaluator)
+ .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+ }
+
+ /**
+ * Interface for classes that would like to refresh the recently updated activity, based on the
+ * configuration change.
+ */
+ interface Evaluator {
+ boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
index 3609837..ed07afd 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
@@ -30,10 +30,12 @@
@Override
void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) {
final int hasCode = System.identityHashCode(ar);
+ snapshot.addReference(TaskSnapshot.REFERENCE_CACHE);
synchronized (mLock) {
final CacheEntry entry = mRunningCache.get(hasCode);
if (entry != null) {
mAppIdMap.remove(entry.topApp);
+ entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
}
mAppIdMap.put(ar, hasCode);
mRunningCache.put(hasCode, new CacheEntry(snapshot, ar));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3303367..08aeede 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1768,6 +1768,7 @@
if (!avoidMoveToFront() && (mService.mHomeProcess == null
|| mService.mHomeProcess.mUid != realCallingUid)
&& (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
+ && !targetTask.isActivityTypeHomeOrRecents()
&& r.mTransitionController.isTransientHide(targetTask)) {
mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
}
@@ -2113,7 +2114,6 @@
if (hostTask == null || targetTask != hostTask) {
return EMBEDDING_DISALLOWED_NEW_TASK;
}
-
return taskFragment.isAllowedToEmbedActivity(starting);
}
@@ -2167,7 +2167,7 @@
// We don't need to start a new activity, and the client said not to do anything
// if that is the case, so this is it! And for paranoia, make sure we have
// correctly resumed the top activity.
- if (!mMovedToFront && mDoResume) {
+ if (!mMovedToFront && mDoResume && !avoidMoveToFront()) {
ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask,
targetTaskTop);
mTargetRootTask.moveToFront("intentActivityFound");
@@ -2196,7 +2196,7 @@
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
- } else if (mDoResume) {
+ } else if (mDoResume && !avoidMoveToFront()) {
// Make sure the root task and its belonging display are moved to topmost.
mTargetRootTask.moveToFront("intentActivityFound");
}
@@ -2961,23 +2961,9 @@
sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);
}
} else {
- TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
+ TaskFragment candidateTf = mAddingToTaskFragment;
if (candidateTf == null) {
- // Puts the activity on the top-most non-isolated navigation TF, unless the
- // activity is launched from the same TF.
- final TaskFragment sourceTaskFragment =
- mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
- final ActivityRecord top = task.getActivity(r -> {
- if (!r.canBeTopRunning()) {
- return false;
- }
- final TaskFragment taskFragment = r.getTaskFragment();
- return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null
- && sourceTaskFragment == taskFragment);
- });
- if (top != null) {
- candidateTf = top.getTaskFragment();
- }
+ candidateTf = findCandidateTaskFragment(task);
}
if (candidateTf != null && candidateTf.isEmbedded()
&& canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
@@ -2995,6 +2981,50 @@
}
/**
+ * Finds a candidate TaskFragment in {@code task} to launch activity, or returns {@code null}
+ * if there's no such a TaskFragment.
+ */
+ @Nullable
+ private TaskFragment findCandidateTaskFragment(@NonNull Task task) {
+ final TaskFragment sourceTaskFragment =
+ mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
+ for (int i = task.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = task.getChildAt(i);
+ final ActivityRecord activity = wc.asActivityRecord();
+ if (activity != null) {
+ if (activity.finishing) {
+ continue;
+ }
+ // Early return if the top child is an Activity.
+ return null;
+ }
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment == null || taskFragment.isRemovalRequested()) {
+ // Skip if the TaskFragment is going to be finished.
+ continue;
+ }
+ if (taskFragment.getActivity(ActivityRecord::canBeTopRunning) == null) {
+ // Skip if there's no activity in this TF can be top running.
+ continue;
+ }
+ if (taskFragment.isIsolatedNav()) {
+ // Stop here if we reach an isolated navigated TF.
+ return null;
+ }
+ if (sourceTaskFragment != null && sourceTaskFragment == taskFragment) {
+ // Choose the taskFragment launched from even if it's pinned.
+ return taskFragment;
+ }
+ if (taskFragment.isPinned()) {
+ // Skip the pinned TaskFragment.
+ continue;
+ }
+ return taskFragment;
+ }
+ return null;
+ }
+
+ /**
* Notifies the client side that {@link #mStartActivity} cannot be embedded to
* {@code taskFragment}.
*/
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c9703d8..0e4f033 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1732,7 +1732,10 @@
// The activity was detached from hierarchy.
return;
}
- activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+
+ if (activity.mDisplayContent.isFixedRotationLaunchingApp(activity)) {
+ activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+ }
// Restore the launch-behind state.
activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index eb1f3b4..f7910b0 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -105,6 +106,7 @@
static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
static final String AUTO_OPT_IN_SAME_UID = "sameUid";
+ static final String AUTO_OPT_IN_COMPAT = "compatibility";
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@@ -303,6 +305,10 @@
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
mAutoOptInCaller = false;
+ } else if (realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_COMPAT) {
+ mAutoOptInReason = AUTO_OPT_IN_COMPAT;
+ mAutoOptInCaller = false;
} else {
mAutoOptInReason = null;
mAutoOptInCaller = false;
@@ -1695,6 +1701,7 @@
return false;
}
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()
+ && state.mResultForRealCaller != null
&& state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c9a5e71..e49cb38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -478,6 +478,8 @@
final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
@Nullable
final CameraStateMonitor mCameraStateMonitor;
+ @Nullable
+ final ActivityRefresher mActivityRefresher;
DisplayFrames mDisplayFrames;
final DisplayUpdater mDisplayUpdater;
@@ -1233,13 +1235,15 @@
mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
if (shouldCreateDisplayRotationCompatPolicy) {
mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
+ mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
- this, mWmService.mH, mCameraStateMonitor);
+ this, mCameraStateMonitor, mActivityRefresher);
mCameraStateMonitor.startListeningToCameraState();
} else {
// These are to satisfy the `final` check.
mCameraStateMonitor = null;
+ mActivityRefresher = null;
mDisplayRotationCompatPolicy = null;
}
@@ -2755,7 +2759,7 @@
@Nullable
Task getTopRootTask() {
- return getRootTask(t -> true);
+ return getRootTask(alwaysTruePredicate());
}
/**
@@ -5009,7 +5013,7 @@
// This should be called after the insets have been dispatched to clients and we have
// committed finish drawing windows.
- mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
+ mInsetsStateController.getImeSourceProvider().checkAndStartShowImePostLayout();
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index eacf9a3..e0cc064 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -18,8 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -32,19 +30,14 @@
import static android.view.Display.TYPE_INTERNAL;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.app.servertransaction.RefreshCallbackItem;
-import android.app.servertransaction.ResumeActivityItem;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.RemoteException;
import android.widget.Toast;
import com.android.internal.R;
@@ -64,48 +57,38 @@
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
*/
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener,
+ ActivityRefresher.Evaluator {
- // 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
- // compat mode is restarted.
- // TODO(b/263114289): Consider associating this delay with a specific activity so that if
- // the new non-camera activity started on top of the camer one we can rotate faster.
- private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
- // Delay for updating display rotation after Camera connection is opened. This delay is
- // selected to be long enough to avoid conflicts with transitions on the app's side.
- // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
- // is flipping between front and rear cameras (in case requested orientation changes at
- // runtime at the same time) or when size compat mode is restarted.
- private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
- CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
- // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
- // client process may not always report the event back to the server, such as process is
- // crashed or got killed.
- private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
-
+ @NonNull
private final DisplayContent mDisplayContent;
+ @NonNull
private final WindowManagerService mWmService;
+ @NonNull
private final CameraStateMonitor mCameraStateMonitor;
- private final Handler mHandler;
+ @NonNull
+ private final ActivityRefresher mActivityRefresher;
@ScreenOrientation
private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
- DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler,
- @NonNull CameraStateMonitor cameraStateMonitor) {
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
+ @NonNull CameraStateMonitor cameraStateMonitor,
+ @NonNull ActivityRefresher activityRefresher) {
// This constructor is called from DisplayContent constructor. Don't use any fields in
// DisplayContent here since they aren't guaranteed to be set.
- mHandler = handler;
mDisplayContent = displayContent;
mWmService = displayContent.mWmService;
mCameraStateMonitor = cameraStateMonitor;
mCameraStateMonitor.addCameraStateListener(this);
+ mActivityRefresher = activityRefresher;
+ mActivityRefresher.addEvaluator(this);
}
/** Releases camera state listener. */
void dispose() {
mCameraStateMonitor.removeCameraStateListener(this);
+ mActivityRefresher.removeEvaluator(this);
}
/**
@@ -169,47 +152,6 @@
}
/**
- * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
- * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
- * camera preview and can lead to sideways or stretching issues persisting even after force
- * rotation.
- */
- void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
- Configuration lastReportedConfig) {
- if (!isTreatmentEnabledForDisplay()
- || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
- || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
- return;
- }
- boolean cycleThroughStop =
- mWmService.mLetterboxConfiguration
- .isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mLetterboxUiController
- .shouldRefreshActivityViaPauseForCameraCompat();
- try {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
- ProtoLog.v(WM_DEBUG_STATES,
- "Refreshing activity for camera compatibility treatment, "
- + "activityRecord=%s", activity);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
- activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
- activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
- activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
- activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
- mHandler.postDelayed(
- () -> onActivityRefreshed(activity),
- REFRESH_CALLBACK_TIMEOUT_MS);
- } catch (RemoteException e) {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
- }
- }
-
- void onActivityRefreshed(@NonNull ActivityRecord activity) {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
- }
-
- /**
* Notifies that animation in {@link ScreenRotationAnimation} has finished.
*
* <p>This class uses this signal as a trigger for notifying the user about forced rotation
@@ -276,14 +218,16 @@
// Refreshing only when configuration changes after rotation or camera split screen aspect ratio
// treatment is enabled
- private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
- Configuration lastReportedConfig) {
+ @Override
+ public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
- return (displayRotationChanged
- || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
+ return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && (displayRotationChanged
+ || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
}
/**
@@ -310,7 +254,6 @@
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
-
/**
* Whether camera compat treatment is applicable for the given activity.
*
@@ -429,6 +372,6 @@
|| !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+ return mActivityRefresher.isActivityRefreshing(topActivity);
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 092ff3d..e03ff688 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -24,7 +24,6 @@
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
-import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
import android.annotation.NonNull;
@@ -52,19 +51,26 @@
private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName();
- /** The token tracking the current IME request or {@code null} otherwise. */
+ /** The token tracking the show IME request, non-null only while a show request is pending. */
@Nullable
- private ImeTracker.Token mImeRequesterStatsToken;
+ private ImeTracker.Token mStatsToken;
+ /** The target that requested to show the IME, non-null only while a show request is pending. */
+ @Nullable
private InsetsControlTarget mImeRequester;
- private Runnable mShowImeRunner;
- private boolean mIsImeLayoutDrawn;
+ /** @see #isImeShowing() */
private boolean mImeShowing;
+ /** The latest received insets source. */
private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime());
/** @see #setFrozen(boolean) */
private boolean mFrozen;
- /** @see #setServerVisible(boolean) */
+ /**
+ * The server visibility of the source provider's window container. This is out of sync with
+ * {@link InsetsSourceProvider#mServerVisible} while {@link #mFrozen} is {@code true}.
+ *
+ * @see #setServerVisible
+ */
private boolean mServerVisible;
ImeInsetsSourceProvider(@NonNull InsetsSource source,
@@ -73,6 +79,7 @@
super(source, stateController, displayContent);
}
+ @Nullable
@Override
InsetsSourceControl getControl(InsetsControlTarget target) {
final InsetsSourceControl control = super.getControl(target);
@@ -124,9 +131,9 @@
/**
* Freeze IME insets source state when required.
*
- * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
+ * <p>When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
* current IME insets state and pending the IME insets state update until setting
- * {@param frozen} as {@code false}.
+ * {@param frozen} as {@code false}.</p>
*/
void setFrozen(boolean frozen) {
if (mFrozen == frozen) {
@@ -223,27 +230,29 @@
/**
* Called from {@link WindowManagerInternal#showImePostLayout}
* when {@link android.inputmethodservice.InputMethodService} requests to show IME
- * on {@param imeTarget}.
+ * on the given control target.
*
- * @param imeTarget imeTarget on which IME request is coming from.
+ * @param imeTarget the control target on which the IME request is coming from.
* @param statsToken the token tracking the current IME request.
*/
- void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+ void scheduleShowImePostLayout(@NonNull InsetsControlTarget imeTarget,
@NonNull ImeTracker.Token statsToken) {
- if (mImeRequesterStatsToken != null) {
- // Cancel the pre-existing stats token, if any.
- // Log state on pre-existing request cancel.
- logShowImePostLayoutState(false /* aborted */);
- ImeTracker.forLogging().onCancelled(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ if (mImeRequester == null) {
+ // Start tracing only on initial scheduled show IME request, to record end-to-end time.
+ Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+ } else {
+ // We already have a scheduled show IME request, cancel the previous statsToken and
+ // continue with the new one.
+ logIsScheduledAndReadyToShowIme(false /* aborted */);
+ ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
}
- mImeRequesterStatsToken = statsToken;
- boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
+ final boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
+ mStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
- checkShowImePostLayout();
+ checkAndStartShowImePostLayout();
// if IME cannot be shown at this time, it is scheduled to be shown.
// once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match,
// it will be shown.
@@ -252,79 +261,58 @@
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
- mShowImeRunner = () -> {
- ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
- ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
- ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
- // Target should still be the same.
- if (isReadyToShowIme()) {
- ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
- ImeTracker.PHASE_WM_SHOW_IME_READY);
- final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
-
- ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
- target.getWindow() != null ? target.getWindow().getName() : "");
- setImeShowing(true);
- target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
- mImeRequesterStatsToken);
- Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
- if (target != mImeRequester && mImeRequester != null) {
- ProtoLog.w(WM_DEBUG_IME,
- "showInsets(ime) was requested by different window: %s ",
- (mImeRequester.getWindow() != null
- ? mImeRequester.getWindow().getName() : ""));
- }
- } else {
- ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
- ImeTracker.PHASE_WM_SHOW_IME_READY);
- }
- // Clear token here so we don't report an error in abortShowImePostLayout().
- mImeRequesterStatsToken = null;
- abortShowImePostLayout();
- };
mDisplayContent.mWmService.requestTraversal();
}
- void checkShowImePostLayout() {
- if (mWindowContainer == null) {
+ /**
+ * Checks whether there is a previously scheduled show IME request and we are ready to show,
+ * in which case also start handling the request.
+ */
+ void checkAndStartShowImePostLayout() {
+ if (!isScheduledAndReadyToShowIme()) {
+ // This can later become ready, so we don't want to cancel the pending request here.
return;
}
- WindowState windowState = mWindowContainer.asWindowState();
- if (windowState == null) {
- throw new IllegalArgumentException("IME insets must be provided by a window.");
+
+ ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
+
+ final InsetsControlTarget target = getControlTarget();
+
+ ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
+ target.getWindow() != null ? target.getWindow().getName() : "");
+ setImeShowing(true);
+ target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, mStatsToken);
+ Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+ if (target != mImeRequester) {
+ ProtoLog.w(WM_DEBUG_IME, "showInsets(ime) was requested by different window: %s ",
+ (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : ""));
}
- // check if IME is drawn
- if (mIsImeLayoutDrawn
- || (isReadyToShowIme()
- && windowState.isDrawn()
- && !windowState.mGivenInsetsPending)) {
- mIsImeLayoutDrawn = true;
- // show IME if InputMethodService requested it to be shown.
- if (mShowImeRunner != null) {
- mShowImeRunner.run();
- }
- }
+ resetShowImePostLayout();
}
- /**
- * Abort any pending request to show IME post layout.
- */
+ /** Aborts the previously scheduled show IME request. */
void abortShowImePostLayout() {
- ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
- if (mImeRequesterStatsToken != null) {
- // Log state on abort.
- logShowImePostLayoutState(true /* aborted */);
- ImeTracker.forLogging().onFailed(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
- mImeRequesterStatsToken = null;
+ if (mImeRequester == null) {
+ return;
}
- mImeRequester = null;
- mIsImeLayoutDrawn = false;
- mShowImeRunner = null;
+ ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
+ Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+ logIsScheduledAndReadyToShowIme(true /* aborted */);
+ ImeTracker.forLogging().onFailed(
+ mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+ resetShowImePostLayout();
}
+ /** Resets the state of the previously scheduled show IME request. */
+ private void resetShowImePostLayout() {
+ mImeRequester = null;
+ mStatsToken = null;
+ }
+
+ /** Checks whether there is a previously scheduled show IME request and we are ready to show. */
@VisibleForTesting
- boolean isReadyToShowIme() {
+ boolean isScheduledAndReadyToShowIme() {
// IMMS#mLastImeTargetWindow always considers focused window as
// IME target, however DisplayContent#computeImeTarget() can compute
// a different IME target.
@@ -334,32 +322,47 @@
// Also, if imeTarget is closing, it would be considered as outdated target.
// TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
// actual IME target.
+ if (mImeRequester == null) {
+ // No show IME request previously scheduled.
+ return false;
+ }
+ if (!mServerVisible || mFrozen) {
+ // The window container is not available and considered visible.
+ // If frozen, the server visibility is not set until unfrozen.
+ return false;
+ }
+ if (mWindowContainer == null) {
+ // No window container set.
+ return false;
+ }
+ final WindowState windowState = mWindowContainer.asWindowState();
+ if (windowState == null) {
+ throw new IllegalArgumentException("IME insets must be provided by a window.");
+ }
+ if (!windowState.isDrawn() || windowState.mGivenInsetsPending) {
+ // The window is not drawn, or it has pending insets.
+ return false;
+ }
final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
- if (dcTarget == null || mImeRequester == null) {
- // Not ready to show if there is no IME layering target, or no IME requester.
+ if (dcTarget == null) {
+ // No IME layering target.
return false;
}
final InsetsControlTarget controlTarget = getControlTarget();
if (controlTarget == null) {
- // Not ready to show if there is no IME control target.
+ // No IME control target.
return false;
}
if (controlTarget != mDisplayContent.getImeTarget(IME_TARGET_CONTROL)) {
- // Not ready to show if control target does not match the one in DisplayContent.
- return false;
- }
- if (!mServerVisible || mFrozen) {
- // Not ready to show if the window container is not available and considered visible.
- // If frozen, the server visibility is not set until unfrozen.
+ // The control target does not match the one in DisplayContent.
return false;
}
if (mStateController.hasPendingControls(controlTarget)) {
- // Not ready to show if control target has pending controls.
+ // The control target has pending controls.
return false;
}
if (getLeash(controlTarget) == null) {
- // Not ready to show if control target has no source control leash (or leash is not
- // ready for dispatching).
+ // The control target has no source control leash (or it is not ready for dispatching).
return false;
}
@@ -371,51 +374,44 @@
|| isAboveImeLayeringTarget(mImeRequester, dcTarget)
|| isImeFallbackTarget(mImeRequester)
|| isImeInputTarget(mImeRequester)
- || sameAsImeControlTarget();
+ || sameAsImeControlTarget(mImeRequester);
}
/**
- * Logs the current state required for showImePostLayout to be triggered.
+ * Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}.
*
- * @param aborted whether the showImePostLayout was aborted or cancelled.
+ * @param aborted whether the scheduled show IME request was aborted or cancelled.
*/
- private void logShowImePostLayoutState(boolean aborted) {
+ private void logIsScheduledAndReadyToShowIme(boolean aborted) {
final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null;
final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
final var controlTarget = getControlTarget();
final var sb = new StringBuilder();
sb.append("showImePostLayout ").append(aborted ? "aborted" : "cancelled");
- sb.append(", mWindowContainer is: ");
- sb.append(mWindowContainer != null ? "non-null" : "null");
+ sb.append(", isScheduledAndReadyToShowIme: ").append(isScheduledAndReadyToShowIme());
+ sb.append(", mImeRequester: ").append(mImeRequester);
+ sb.append(", serverVisible: ").append(mServerVisible);
+ sb.append(", frozen: ").append(mFrozen);
+ sb.append(", mWindowContainer is: ").append(mWindowContainer != null ? "non-null" : "null");
sb.append(", windowState: ").append(windowState);
if (windowState != null) {
- sb.append(", windowState.isDrawn(): ");
- sb.append(windowState.isDrawn());
- sb.append(", windowState.mGivenInsetsPending: ");
- sb.append(windowState.mGivenInsetsPending);
+ sb.append(", isDrawn: ").append(windowState.isDrawn());
+ sb.append(", mGivenInsetsPending: ").append(windowState.mGivenInsetsPending);
}
- sb.append(", mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn);
- sb.append(", mShowImeRunner: ").append(mShowImeRunner);
- sb.append(", mImeRequester: ").append(mImeRequester);
sb.append(", dcTarget: ").append(dcTarget);
sb.append(", controlTarget: ").append(controlTarget);
- sb.append("\n");
- sb.append("isReadyToShowIme(): ").append(isReadyToShowIme());
if (mImeRequester != null && dcTarget != null && controlTarget != null) {
- sb.append(", controlTarget == DisplayContent.controlTarget: ");
+ sb.append("\n");
+ sb.append("controlTarget == DisplayContent.controlTarget: ");
sb.append(controlTarget == mDisplayContent.getImeTarget(IME_TARGET_CONTROL));
sb.append(", hasPendingControls: ");
sb.append(mStateController.hasPendingControls(controlTarget));
- sb.append(", serverVisible: ");
- sb.append(mServerVisible);
- sb.append(", frozen: ");
- sb.append(mFrozen);
- sb.append(", leash is: ");
- sb.append(getLeash(controlTarget) != null ? "non-null" : "null");
- sb.append(", control is: ");
- sb.append(mControl != null ? "non-null" : "null");
- sb.append(", mIsLeashReadyForDispatching: ");
- sb.append(mIsLeashReadyForDispatching);
+ final boolean hasLeash = getLeash(controlTarget) != null;
+ sb.append(", leash is: ").append(hasLeash ? "non-null" : "null");
+ if (!hasLeash) {
+ sb.append(", control is: ").append(mControl != null ? "non-null" : "null");
+ sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching);
+ }
sb.append(", isImeLayeringTarget: ");
sb.append(isImeLayeringTarget(mImeRequester, dcTarget));
sb.append(", isAboveImeLayeringTarget: ");
@@ -425,7 +421,7 @@
sb.append(", isImeInputTarget: ");
sb.append(isImeInputTarget(mImeRequester));
sb.append(", sameAsImeControlTarget: ");
- sb.append(sameAsImeControlTarget());
+ sb.append(sameAsImeControlTarget(mImeRequester));
}
Slog.d(TAG, sb.toString());
}
@@ -445,19 +441,18 @@
&& dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
}
- private boolean isImeFallbackTarget(InsetsControlTarget target) {
+ private boolean isImeFallbackTarget(@NonNull InsetsControlTarget target) {
return target == mDisplayContent.getImeFallback();
}
- private boolean isImeInputTarget(InsetsControlTarget target) {
+ private boolean isImeInputTarget(@NonNull InsetsControlTarget target) {
return target == mDisplayContent.getImeInputTarget();
}
- private boolean sameAsImeControlTarget() {
- final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
- return target == mImeRequester
- && (mImeRequester.getWindow() == null
- || !isImeTargetWindowClosing(mImeRequester.getWindow()));
+ private boolean sameAsImeControlTarget(@NonNull InsetsControlTarget target) {
+ final InsetsControlTarget controlTarget = getControlTarget();
+ return controlTarget == target
+ && (target.getWindow() == null || !isImeTargetWindowClosing(target.getWindow()));
}
private static boolean isImeTargetWindowClosing(@NonNull WindowState win) {
@@ -467,16 +462,15 @@
|| win.mActivityRecord.willCloseOrEnterPip());
}
- private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
+ private boolean isTargetChangedWithinActivity(@NonNull InsetsControlTarget target) {
// We don't consider the target out of the activity.
- if (target == null || target.getWindow() == null) {
+ if (target.getWindow() == null) {
return false;
}
return mImeRequester != target
- && mImeRequester != null && mShowImeRunner != null
+ && mImeRequester != null
&& mImeRequester.getWindow() != null
- && mImeRequester.getWindow().mActivityRecord
- == target.getWindow().mActivityRecord;
+ && mImeRequester.getWindow().mActivityRecord == target.getWindow().mActivityRecord;
}
// ---------------------------------------------------------------------------------------
@@ -509,7 +503,6 @@
if (imeRequesterWindow != null) {
imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
}
- proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4400ed2..2288fe9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -198,7 +198,7 @@
if (mControllable) {
mWindowContainer.setControllableInsetProvider(this);
if (mPendingControlTarget != mControlTarget) {
- updateControlForTarget(mPendingControlTarget, true /* force */);
+ mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
}
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index dfee164..7a1f57b 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -389,7 +389,7 @@
newControlTargets.clear();
// Check for and try to run the scheduled show IME request (if it exists), as we
// now applied the surface transaction and notified the target of the new control.
- getImeSourceProvider().checkShowImePostLayout();
+ getImeSourceProvider().checkAndStartShowImePostLayout();
});
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9e16b8a..16d7b4f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -260,7 +260,7 @@
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
- private boolean mIsRefreshAfterRotationRequested;
+ private boolean mIsRefreshRequested;
@NonNull
private final OptProp mIgnoreRequestedOrientationOptProp;
@@ -571,15 +571,14 @@
}
/**
- * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
- * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+ * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
*/
- boolean isRefreshAfterRotationRequested() {
- return mIsRefreshAfterRotationRequested;
+ boolean isRefreshRequested() {
+ return mIsRefreshRequested;
}
- void setIsRefreshAfterRotationRequested(boolean isRequested) {
- mIsRefreshAfterRotationRequested = isRequested;
+ void setIsRefreshRequested(boolean isRequested) {
+ mIsRefreshRequested = isRequested;
}
boolean isOverrideRespectRequestedOrientationEnabled() {
@@ -989,6 +988,10 @@
}
}
+ boolean isLetterboxEducationEnabled() {
+ return mLetterboxConfiguration.getIsEducationEnabled();
+ }
+
/**
* Whether we use split screen aspect ratio for the activity when camera compat treatment
* is active because the corresponding config is enabled and activity supports resizing.
@@ -1064,7 +1067,7 @@
* thin letteboxing
*/
boolean allowVerticalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingReachability()) {
+ if (!Flags.disableThinLetterboxingPolicy()) {
return true;
}
// When the flag is enabled we allow vertical reachability only if the
@@ -1077,7 +1080,7 @@
* thin letteboxing
*/
boolean allowHorizontalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingReachability()) {
+ if (!Flags.disableThinLetterboxingPolicy()) {
return true;
}
// When the flag is enabled we allow horizontal reachability only if the
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index 498182d..3606a34 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -41,8 +41,12 @@
PerfettoTransitionTracer() {
Producer.init(InitArguments.DEFAULTS);
- mDataSource.register(
- new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .build();
+ mDataSource.register(params);
}
/**
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6dec712..72f592b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -64,7 +64,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java
index 8680436..1e6ee7d 100644
--- a/services/core/java/com/android/server/wm/SnapshotCache.java
+++ b/services/core/java/com/android/server/wm/SnapshotCache.java
@@ -92,6 +92,7 @@
if (entry != null) {
mAppIdMap.remove(entry.topApp);
mRunningCache.remove(id);
+ entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
}
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 3578971..42ca7b4 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -253,6 +253,7 @@
PersistInfoProvider provider) {
super(provider, userId);
mId = id;
+ snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST);
mSnapshot = snapshot;
}
@@ -289,6 +290,7 @@
if (failed) {
deleteSnapshot(mId, mUserId, mPersistInfoProvider);
}
+ mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8bd7b5f..a555388 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -297,6 +297,10 @@
ActivityRecord mTranslucentActivityWaiting = null;
ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>();
+ // The topmost Activity that was converted to translucent for scene transition, which should
+ // be converted from translucent once the transition is completed, or the app died.
+ private ActivityRecord mPendingConvertFromTranslucentActivity = null;
+
/**
* Set when we know we are going to be calling updateConfiguration()
* soon, so want to skip intermediate config checks.
@@ -3448,6 +3452,8 @@
// Whether the direct top activity is eligible for letterbox education.
appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
&& top.isEligibleForLetterboxEducation();
+ appCompatTaskInfo.isLetterboxEducationEnabled = top != null
+ && top.mLetterboxUiController.isLetterboxEducationEnabled();
// Whether the direct top activity requested showing camera compat control.
appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = isTopActivityResumed
? top.getCameraCompatControlState()
@@ -4986,6 +4992,27 @@
}
}
+ void abortTranslucentActivityWaiting(@NonNull ActivityRecord r) {
+ if (r != mTranslucentActivityWaiting && r != mPendingConvertFromTranslucentActivity) {
+ return;
+ }
+
+ if (mTranslucentActivityWaiting != null) {
+ if (!mTranslucentActivityWaiting.finishing) {
+ mTranslucentActivityWaiting.setOccludesParent(true);
+ }
+ mTranslucentActivityWaiting = null;
+ }
+ if (mPendingConvertFromTranslucentActivity != null) {
+ if (!mPendingConvertFromTranslucentActivity.finishing) {
+ mPendingConvertFromTranslucentActivity.setOccludesParent(true);
+ }
+ mPendingConvertFromTranslucentActivity = null;
+ }
+ mUndrawnActivitiesBelowTopTranslucent.clear();
+ mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+ }
+
void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -5000,10 +5027,19 @@
void convertActivityToTranslucent(ActivityRecord r) {
mTranslucentActivityWaiting = r;
+ mPendingConvertFromTranslucentActivity = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
}
+ void convertActivityFromTranslucent(ActivityRecord r) {
+ if (r != mPendingConvertFromTranslucentActivity) {
+ Slog.e(TAG, "convertFromTranslucent expects " + mPendingConvertFromTranslucentActivity
+ + " but is " + r);
+ }
+ mPendingConvertFromTranslucentActivity = null;
+ }
+
/**
* Called as activities below the top translucent activity are redrawn. When the last one is
* redrawn notify the top activity by calling
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 21e7a8d..586f3c3 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -247,6 +247,7 @@
break;
case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
+ ((TaskSnapshot) msg.obj).removeReference(TaskSnapshot.REFERENCE_BROADCAST);
break;
case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
@@ -485,6 +486,7 @@
* Notify listeners that the snapshot of a task has changed.
*/
void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ snapshot.addReference(TaskSnapshot.REFERENCE_BROADCAST);
final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG,
taskId, 0, snapshot);
forAllLocalListeners(mNotifyTaskSnapshotChanged, msg);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 2c27b98..eff8315 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -223,7 +223,7 @@
@VisibleForTesting
Task getTopRootTask() {
- return getRootTask(t -> true);
+ return getRootTask(alwaysTruePredicate());
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6a7f60b..26e4eaa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -40,6 +40,8 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
@@ -354,14 +356,21 @@
/**
* Whether the activity navigation should be isolated. That is, Activities cannot be launched
- * on an isolated TaskFragment, unless the activity is launched from an Activity in the same
- * isolated TaskFragment, or explicitly requested to be launched to.
- * <p>
- * Note that only an embedded TaskFragment can be isolated.
+ * on an isolated TaskFragment unless explicitly requested to be launched to.
*/
private boolean mIsolatedNav;
/**
+ * Whether the TaskFragment to be pinned.
+ * <p>
+ * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other
+ * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the
+ * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or
+ * explicitly requested to. Non-embeddable activities are not restricted to.
+ */
+ private boolean mPinned;
+
+ /**
* Whether the TaskFragment should move to bottom of task when any activity below it is
* launched in clear top mode.
*/
@@ -515,6 +524,18 @@
}
/**
+ * Sets whether this TaskFragment {@link #isPinned()}.
+ * <p>
+ * Note that this is no-op if the TaskFragment is not {@link #isEmbedded() embedded}.
+ */
+ void setPinned(boolean pinned) {
+ if (!isEmbedded()) {
+ return;
+ }
+ mPinned = pinned;
+ }
+
+ /**
* Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
* transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
* will wait until the TaskFragment becomes non-empty or other conditions are met. Default
@@ -532,6 +553,15 @@
return isEmbedded() && mIsolatedNav;
}
+ /**
+ * Indicates whether this TaskFragment is pinned.
+ *
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
+ */
+ boolean isPinned() {
+ return isEmbedded() && mPinned;
+ }
+
TaskFragment getAdjacentTaskFragment() {
return mAdjacentTaskFragment;
}
@@ -564,7 +594,6 @@
}
void setResumedActivity(ActivityRecord r, String reason) {
- warnForNonLeafTaskFragment("setResumedActivity");
if (mResumedActivity == r) {
return;
}
@@ -850,15 +879,6 @@
return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null;
}
- /**
- * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}.
- */
- private void warnForNonLeafTaskFragment(String func) {
- if (!isLeafTaskFragment()) {
- Slog.w(TAG, func + " on non-leaf task fragment " + this);
- }
- }
-
boolean hasDirectChildActivities() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asActivityRecord() != null) {
@@ -935,7 +955,6 @@
*/
void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
String reason) {
- warnForNonLeafTaskFragment("onActivityStateChanged");
if (record == mResumedActivity && state != RESUMED) {
setResumedActivity(null, reason + " - onActivityStateChanged");
}
@@ -965,7 +984,6 @@
* @return {@code true} if the process of the pausing activity is died.
*/
boolean handleAppDied(WindowProcessController app) {
- warnForNonLeafTaskFragment("handleAppDied");
boolean isPausingDied = false;
if (mPausingActivity != null && mPausingActivity.app == app) {
ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
@@ -2206,7 +2224,7 @@
static class ConfigOverrideHint {
@Nullable DisplayInfo mTmpOverrideDisplayInfo;
@Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
- boolean mUseOverrideInsetsForStableBounds;
+ boolean mUseOverrideInsetsForConfig;
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@@ -2239,11 +2257,11 @@
@NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
DisplayInfo overrideDisplayInfo = null;
ActivityRecord.CompatDisplayInsets compatInsets = null;
- boolean useOverrideInsetsForStableBounds = false;
+ boolean useOverrideInsetsForConfig = false;
if (overrideHint != null) {
overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
compatInsets = overrideHint.mTmpCompatInsets;
- useOverrideInsetsForStableBounds = overrideHint.mUseOverrideInsetsForStableBounds;
+ useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig;
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided
// display info.
@@ -2307,6 +2325,7 @@
}
}
+ boolean insetsOverrideApplied = false;
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
|| inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
@@ -2323,7 +2342,7 @@
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
- useOverrideInsetsForStableBounds);
+ useOverrideInsetsForConfig);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2340,8 +2359,21 @@
intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
compatInsets.mStableInsets[rotation]);
outAppBounds.set(mTmpNonDecorBounds);
+ } else if (useOverrideInsetsForConfig) {
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final DisplayPolicy.DecorInsets.Info decorInsets = mDisplayContent
+ .getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+ mTmpStableBounds.set(outAppBounds);
+ mTmpStableBounds.inset(decorInsets.mOverrideConfigInsets);
+ outAppBounds.inset(decorInsets.mOverrideNonDecorInsets);
+ mTmpNonDecorBounds.set(outAppBounds);
+ // Record the override apply to avoid duplicated check.
+ insetsOverrideApplied = true;
} else {
- // Set to app bounds because it excludes decor insets.
mTmpNonDecorBounds.set(outAppBounds);
mTmpStableBounds.set(outAppBounds);
}
@@ -2383,6 +2415,11 @@
// from the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
+ } else if (insetsOverrideApplied) {
+ // The smallest width should also consider insets. If the insets are overridden,
+ // use the overridden value.
+ inOutConfig.smallestScreenWidthDp =
+ Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
}
// otherwise, it will just inherit
}
@@ -2895,6 +2932,13 @@
return !mCreatedByOrganizer || mIsRemovalRequested;
}
+ /**
+ * Returns whether this TaskFragment is going to be removed.
+ */
+ boolean isRemovalRequested() {
+ return mIsRemovalRequested;
+ }
+
@Override
void removeChild(WindowContainer child) {
removeChild(child, true /* removeSelfIfPossible */);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index b69ac1b..64b9df5 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -35,9 +35,11 @@
void putSnapshot(Task task, TaskSnapshot snapshot) {
synchronized (mLock) {
+ snapshot.addReference(TaskSnapshot.REFERENCE_CACHE);
final CacheEntry entry = mRunningCache.get(task.mTaskId);
if (entry != null) {
mAppIdMap.remove(entry.topApp);
+ entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
}
final ActivityRecord top = task.getTopMostActivity();
mAppIdMap.put(top, task.mTaskId);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d70ca02..edbba92 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -116,6 +116,7 @@
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+import com.android.server.wm.utils.AlwaysTruePredicate;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -2019,29 +2020,34 @@
callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
+ @SuppressWarnings("unchecked")
+ static <T> Predicate<T> alwaysTruePredicate() {
+ return (Predicate<T>) AlwaysTruePredicate.INSTANCE;
+ }
+
ActivityRecord getActivityAbove(ActivityRecord r) {
- return getActivity((above) -> true, r,
+ return getActivity(alwaysTruePredicate(), r /* boundary */,
false /*includeBoundary*/, false /*traverseTopToBottom*/);
}
ActivityRecord getActivityBelow(ActivityRecord r) {
- return getActivity((below) -> true, r,
+ return getActivity(alwaysTruePredicate(), r /* boundary */,
false /*includeBoundary*/, true /*traverseTopToBottom*/);
}
ActivityRecord getBottomMostActivity() {
- return getActivity((r) -> true, false /*traverseTopToBottom*/);
+ return getActivity(alwaysTruePredicate(), false /* traverseTopToBottom */);
}
ActivityRecord getTopMostActivity() {
- return getActivity((r) -> true, true /*traverseTopToBottom*/);
+ return getActivity(alwaysTruePredicate(), true /* traverseTopToBottom */);
}
ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
// Break down into 4 calls to avoid object creation due to capturing input params.
if (includeFinishing) {
if (includeOverlays) {
- return getActivity((r) -> true);
+ return getActivity(alwaysTruePredicate());
}
return getActivity((r) -> !r.isTaskOverlay());
} else if (includeOverlays) {
@@ -2220,21 +2226,17 @@
}
}
- Task getTaskAbove(Task t) {
- return getTask(
- (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
- }
-
Task getTaskBelow(Task t) {
- return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ return getTask(alwaysTruePredicate(), t /* boundary */,
+ false /* includeBoundary */, true /* traverseTopToBottom */);
}
Task getBottomMostTask() {
- return getTask((t) -> true, false /*traverseTopToBottom*/);
+ return getTask(alwaysTruePredicate(), false /* traverseTopToBottom */);
}
Task getTopMostTask() {
- return getTask((t) -> true, true /*traverseTopToBottom*/);
+ return getTask(alwaysTruePredicate(), true /* traverseTopToBottom */);
}
Task getTask(Predicate<Task> callback) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e02e5be..b603551 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8302,7 +8302,6 @@
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
- Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
imeTarget = controlTarget.getWindow();
// If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 90e7bd7..99c4736 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -39,6 +39,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1627,6 +1628,11 @@
}
break;
}
+ case OP_TYPE_SET_PINNED: {
+ final boolean pinned = operation.getBooleanValue();
+ taskFragment.setPinned(pinned);
+ break;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java
new file mode 100644
index 0000000..49dcb6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import java.util.function.Predicate;
+
+/** A simple Predicate to avoid synthetic allocation of lambda expression "o -> true". */
+public class AlwaysTruePredicate implements Predicate<Object> {
+
+ public static final AlwaysTruePredicate INSTANCE = new AlwaysTruePredicate();
+
+ private AlwaysTruePredicate() {
+ }
+
+ @Override
+ public boolean test(Object o) {
+ return true;
+ }
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a01c123..97f1e19 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -122,7 +122,6 @@
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
- jmethodID onPointerDisplayIdChanged;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -423,7 +422,7 @@
std::set<int32_t> disabledInputDevices{};
// Associated Pointer controller display.
- ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId pointerDisplayId{ui::LogicalDisplayId::DEFAULT};
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
@@ -786,12 +785,6 @@
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
InputReaderConfiguration::Change::DISPLAY_INFO);
-
- // Notify the system.
- JNIEnv* env = jniEnv();
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
- position.x, position.y);
- checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -1886,7 +1879,7 @@
jstring nameObj, jint pid) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (displayId == ui::ADISPLAY_ID_NONE.val()) {
+ if (ui::LogicalDisplayId{displayId} == ui::LogicalDisplayId::INVALID) {
std::string message = "InputChannel used as a monitor must be associated with a display";
jniThrowRuntimeException(env, message.c_str());
return nullptr;
@@ -2727,6 +2720,11 @@
im->setInputMethodConnectionIsActive(isActive);
}
+static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2835,6 +2833,7 @@
{"setAccessibilityStickyKeysEnabled", "(Z)V",
(void*)nativeSetAccessibilityStickyKeysEnabled},
{"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
+ {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
};
#define FIND_CLASS(var, className) \
@@ -2927,9 +2926,6 @@
"dispatchUnhandledKey",
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
- GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
- "(IFF)V");
-
GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz,
"notifyStickyModifierStateChanged", "(II)V");
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 6143f1d..610b502 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -746,6 +746,20 @@
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
</xs:element>
+ <!-- list of supported modes for low power. Each point corresponds to one mode.
+ Mode format is : first = refreshRate, second = vsyncRate. E.g. :
+ <lowPowerSupportedModes>
+ <point>
+ <first>60</first> // refreshRate
+ <second>60</second> //vsyncRate
+ </point>
+ ....
+ </lowPowerSupportedModes>
+ -->
+ <xs:element type="nonNegativeFloatToFloatMap" name="lowPowerSupportedModes" minOccurs="0">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:complexType>
<xs:complexType name="refreshRateZoneProfiles">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 45ec8f2..203a6d9 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -360,6 +360,7 @@
method public final java.math.BigInteger getDefaultRefreshRateInHbmHdr();
method public final java.math.BigInteger getDefaultRefreshRateInHbmSunlight();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
+ method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getLowPowerSupportedModes();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
method public final com.android.server.display.config.RefreshRateZoneProfiles getRefreshRateZoneProfiles();
method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
@@ -367,6 +368,7 @@
method public final void setDefaultRefreshRateInHbmHdr(java.math.BigInteger);
method public final void setDefaultRefreshRateInHbmSunlight(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+ method public final void setLowPowerSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setRefreshRateZoneProfiles(com.android.server.display.config.RefreshRateZoneProfiles);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d114337..d733762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -217,7 +217,7 @@
<V> void setLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
- @Nullable PolicyValue<V> value,
+ @NonNull PolicyValue<V> value,
int userId,
boolean skipEnforcePolicy) {
Objects.requireNonNull(policyDefinition);
@@ -313,6 +313,7 @@
}
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
write();
+ applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
@@ -400,7 +401,7 @@
* else remove the policy from child.
*/
private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
- EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+ EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) {
if (policyDefinition.isInheritable()) {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> userInfos = mUserManager.getProfiles(userId);
@@ -1742,14 +1743,17 @@
}
}
- <V> void reapplyAllPoliciesLocked() {
+ <V> void reapplyAllPoliciesOnBootLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
// Policy definition and value will always be of the same type
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
- enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+ if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+ enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+ }
}
for (int i = 0; i < mLocalPolicies.size(); i++) {
int userId = mLocalPolicies.keyAt(i);
@@ -1758,10 +1762,11 @@
// Policy definition and value will always be of the same type
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue =
- (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
- enforcePolicy(policyDefinition, policyValue, userId);
-
+ if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+ enforcePolicy(policyDefinition, policyValue, userId);
+ }
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f75803f..85d2a0d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -339,6 +339,7 @@
import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PackagePolicy;
+import android.app.admin.PackageSetPolicyValue;
import android.app.admin.ParcelableGranteeMap;
import android.app.admin.ParcelableResource;
import android.app.admin.PasswordMetrics;
@@ -349,7 +350,6 @@
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.PackageSetPolicyValue;
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
@@ -2718,6 +2718,7 @@
mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+ mInjector.runCryptoSelfTest();
} else {
synchronized (getLockObject()) {
mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
@@ -3350,7 +3351,7 @@
break;
case SystemService.PHASE_SYSTEM_SERVICES_READY:
synchronized (getLockObject()) {
- mDevicePolicyEngine.reapplyAllPoliciesLocked();
+ mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked();
}
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
@@ -11442,7 +11443,7 @@
}
setBackwardsCompatibleAppRestrictions(
caller, packageName, restrictions, caller.getUserHandle());
- } else if (Flags.dmrhCanSetAppRestriction()) {
+ } else if (Flags.dmrhSetAppRestrictions()) {
final boolean isRoleHolder;
if (who != null) {
// DO or PO
@@ -11483,10 +11484,6 @@
new BundlePolicyValue(restrictions),
affectedUserId);
}
- Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
- changeIntent.setPackage(packageName);
- changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
} else {
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setApplicationRestrictions(packageName, restrictions,
@@ -12844,7 +12841,7 @@
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
- } else if (Flags.dmrhCanSetAppRestriction()) {
+ } else if (Flags.dmrhSetAppRestrictions()) {
final boolean isRoleHolder;
if (who != null) {
// Caller is DO or PO. They cannot call this on parent
@@ -15769,8 +15766,13 @@
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
userId);
List<Bundle> restrictions = new ArrayList<>();
- for (EnforcingAdmin admin : policies.keySet()) {
- restrictions.add(policies.get(admin).getValue());
+ for (PolicyValue<Bundle> policyValue: policies.values()) {
+ Bundle value = policyValue.getValue();
+ // Probably not necessary since setApplicationRestrictions only sets non-empty
+ // Bundle, but just in case.
+ if (value != null && !value.isEmpty()) {
+ restrictions.add(value);
+ }
}
return mInjector.binderWithCleanCallingIdentity(() -> {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 1000bfa..cbd2847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -52,7 +52,18 @@
EnterpriseSpecificIdCalculator(Context context) {
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
- mImei = telephonyService.getImei(0);
+
+ String imei;
+ try {
+ imei = telephonyService.getImei(0);
+ } catch (UnsupportedOperationException doesNotSupportGms) {
+ // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
+ // However that runs the risk of changing a device's existing ESID if on these devices
+ // telephonyService.getImei() actually returns non-null even when the device does not
+ // declare FEATURE_TELEPHONY_GSM.
+ imei = null;
+ }
+ mImei = imei;
String meid;
try {
meid = telephonyService.getMeid(0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8d980b5..8bec384 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -51,6 +51,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
final class PolicyDefinition<V> {
@@ -82,6 +83,10 @@
// them.
private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
+ // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the
+ // callback in other cases such as device reboots.
+ private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5;
+
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
@@ -231,11 +236,11 @@
// Don't need to take in a resolution mechanism since its never used, but might
// need some refactoring to not always assume a non-null mechanism.
new MostRecent<>(),
- POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
- // Application restrictions are now stored and retrieved from DPMS, so no
- // enforcing is required, however DPMS calls into UM to set restrictions for
- // backwards compatibility.
- (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ // Only invoke the enforcement callback during policy change and not other state
+ POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
+ | POLICY_FLAG_NON_COEXISTABLE_POLICY
+ | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
+ PolicyEnforcerCallbacks::setApplicationRestrictions,
new BundlePolicySerializer());
/**
@@ -581,6 +586,10 @@
return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
}
+ boolean shouldSkipEnforcementIfNotChanged() {
+ return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0;
+ }
+
@Nullable
PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
return mResolutionMechanism.resolve(adminsPolicy);
@@ -610,7 +619,7 @@
* {@link Object#equals} implementation.
*/
private PolicyDefinition(
- PolicyKey key,
+ @NonNull PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
@@ -622,11 +631,12 @@
* {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- PolicyKey policyKey,
+ @NonNull PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
+ Objects.requireNonNull(policyKey);
mPolicyKey = policyKey;
mResolutionMechanism = resolutionMechanism;
mPolicyFlags = policyFlags;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 09eef45..04d277e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -37,11 +37,13 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -172,6 +174,29 @@
return true;
}
+
+ /**
+ * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing
+ * it to UMS) is required. Only need to send broadcast to the target user here as we rely on
+ * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple
+ * profiles. The broadcast should only be sent when an application restriction is set, so we
+ * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
+ * when the policy is set, and not during system boot or other situations.
+ */
+ static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
+ PolicyKey policyKey) {
+ Binder.withCleanCallingIdentity(() -> {
+ PackagePolicyKey key = (PackagePolicyKey) policyKey;
+ String packageName = key.getPackageName();
+ Objects.requireNonNull(packageName);
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
+ });
+ return true;
+ }
+
private static class BlockingCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING
new file mode 100644
index 0000000..55b355c
--- /dev/null
+++ b/services/people/java/com/android/server/people/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.people.data"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 9e4f821..d307200 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1276,7 +1276,23 @@
packageName,
permissionName
)
- else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+ else ->
+ permissionAllowlist.getProductSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getVendorSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index 6a82f16..3e87c6f 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -543,6 +544,18 @@
return future.get();
}
+ @Test
+ public void onBindingDied_referenceLost_doesNotThrow() {
+ TransportConnection.TransportConnectionMonitor transportConnectionMonitor =
+ new TransportConnection.TransportConnectionMonitor(
+ mContext, /* transportConnection= */ null);
+ doThrow(new IllegalArgumentException("Service not registered")).when(
+ mContext).unbindService(any());
+
+ // Test no exception is thrown
+ transportConnectionMonitor.onBindingDied(mTransportComponent);
+ }
+
private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
ArgumentCaptor<ServiceConnection> connectionCaptor =
ArgumentCaptor.forClass(ServiceConnection.class);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 1d225ba..221a991 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -36,9 +36,10 @@
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static java.util.Objects.requireNonNull;
+
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -72,7 +73,10 @@
super.setUp();
mVisibilityApplier =
(DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
- mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
+ synchronized (ImfLock.class) {
+ mInputMethodManagerService.setAttachedClientForTesting(requireNonNull(
+ mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient)));
+ }
}
@Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
new file mode 100644
index 0000000..50804da
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class InputMethodInfoUtilsTest {
+
+ @Test
+ public void testMarshalSameObject() {
+ final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final byte[] buf = InputMethodInfoUtils.marshal(imi);
+
+ assertArrayEquals("The same value must be returned when called multiple times",
+ buf, InputMethodInfoUtils.marshal(imi));
+ assertArrayEquals("The same value must be returned when called multiple times",
+ buf, InputMethodInfoUtils.marshal(imi));
+ }
+
+ @Test
+ public void testMarshalDifferentObjects() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0));
+
+ assertFalse("Different inputs must yield different byte patterns", Arrays.equals(
+ InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2)));
+ }
+
+ @NonNull
+ private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ return Objects.requireNonNull(parcel.readTypedObject(creator));
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+
+ @Test
+ public void testUnmarshalSameObject() {
+ final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi),
+ InputMethodInfo.CREATOR);
+ assertEquals(imi.getPackageName(), cloned.getPackageName());
+ assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount());
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index cff2265..2bbd3c0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +46,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
@@ -53,6 +55,7 @@
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IInputMethodSession;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputBindResult;
@@ -104,6 +107,7 @@
@Mock protected UserManagerInternal mMockUserManagerInternal;
@Mock protected InputMethodBindingController mMockInputMethodBindingController;
@Mock protected IInputMethodClient mMockInputMethodClient;
+ @Mock protected IInputMethodSession mMockInputMethodSession;
@Mock protected IBinder mWindowToken;
@Mock protected IRemoteInputConnection mMockRemoteInputConnection;
@Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection;
@@ -123,6 +127,7 @@
protected IInputMethodInvoker mMockInputMethodInvoker;
protected InputMethodManagerService mInputMethodManagerService;
protected ServiceThread mServiceThread;
+ protected ServiceThread mPackageMonitorThread;
protected boolean mIsLargeScreen;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@@ -218,11 +223,16 @@
mServiceThread =
new ServiceThread(
- "TestServiceThread",
- Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
- false);
+ "immstest1",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
+ mPackageMonitorThread =
+ new ServiceThread(
+ "immstest2",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
- unusedUserId -> mMockInputMethodBindingController);
+ mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
// Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
@@ -246,6 +256,7 @@
// Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
+ createSessionForClient(mMockInputMethodClient);
}
@After
@@ -254,6 +265,10 @@
mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
}
+ if (mPackageMonitorThread != null) {
+ mPackageMonitorThread.quitSafely();
+ }
+
if (mServiceThread != null) {
mServiceThread.quitSafely();
}
@@ -295,4 +310,13 @@
.hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */,
anyInt() /* flags */, any() /* resultReceiver */);
}
+
+ protected void createSessionForClient(IInputMethodClient client) {
+ synchronized (ImfLock.class) {
+ ClientState cs = mInputMethodManagerService.getClientStateLocked(client);
+ cs.mCurSession = new InputMethodManagerService.SessionState(cs,
+ mMockInputMethodInvoker, mMockInputMethodSession, mock(
+ InputChannel.class));
+ }
+ }
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
new file mode 100644
index 0000000..5e3bc56
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+public final class InputMethodMapTest {
+
+ @NonNull
+ private static InputMethodMap toMap(InputMethodInfo... list) {
+ final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>();
+ for (var imi : list) {
+ map.put(imi.getId(), imi);
+ }
+ return InputMethodMap.of(map);
+ }
+
+ @Test
+ public void testEqualsSameObject() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ final var map = toMap(imi1, imi2);
+ assertTrue("Must return true for the same instance",
+ InputMethodMap.equals(map, map));
+ }
+
+ @Test
+ public void testEqualsEquivalentObject() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ assertTrue("Must return true for the equivalent instances",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi2)));
+
+ assertTrue("Must return true for the equivalent instances",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi2, imi1)));
+ }
+
+ @Test
+ public void testEqualsDifferentKeys() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1), toMap(imi1, imi2)));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1)));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi3)));
+ }
+
+ @Test
+ public void testEqualsDifferentValues() {
+ final var imi1_without_subtypes =
+ createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi1_with_subtypes =
+ createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ assertFalse("Must return false if values are different",
+ InputMethodMap.equals(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
+ assertFalse("Must return false if values are different",
+ InputMethodMap.equals(
+ toMap(imi1_without_subtypes, imi2),
+ toMap(imi1_with_subtypes, imi2)));
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
new file mode 100644
index 0000000..c51ff87f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public final class TestUtils {
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod"));
+
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod"));
+
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod"));
+
+ /**
+ * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number.
+ *
+ * @param count The number of fake {@link InputMethodSubtype} objects
+ * @return The list of fake {@link InputMethodSubtype} objects
+ */
+ @NonNull
+ public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) {
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count);
+ for (int i = 0; i < count; ++i) {
+ subtypes.add(
+ new InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(i + 0x100)
+ .setLanguageTag("en-US")
+ .setSubtypeNameOverride("TestSubtype" + i)
+ .build());
+ }
+ return subtypes;
+ }
+
+ /**
+ * Creates a fake {@link InputMethodInfo} for unit testing.
+ *
+ * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo}
+ * @param subtypes A list of (fake) {@link InputMethodSubtype}
+ * @return a fake {@link InputMethodInfo} object
+ */
+ @NonNull
+ public static InputMethodInfo createFakeInputMethodInfo(
+ @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = componentName.getPackageName();
+ ai.enabled = true;
+
+ final ServiceInfo si = new ServiceInfo();
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = componentName.getPackageName();
+ si.name = componentName.getClassName();
+ si.exported = true;
+ si.nonLocalizedLabel = "Fake Label";
+
+ final ResolveInfo ri = new ResolveInfo();
+ ri.serviceInfo = si;
+
+ return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */,
+ subtypes, 0 /* isDefaultResId */, false /* forceDefault */);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index 9e11fa2..e545a49 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -71,7 +71,7 @@
@Mock
private Installer mInstaller;
- private Object mInstallLock;
+ private PackageManagerTracedLock mInstallLock;
@Before
public void setup() {
@@ -79,7 +79,7 @@
TEST_USER.serialNumber = TEST_USER_SERIAL;
Context ctx = InstrumentationRegistry.getContext();
FileUtils.deleteContents(ctx.getCacheDir());
- mInstallLock = new Object();
+ mInstallLock = new PackageManagerTracedLock();
MockitoAnnotations.initMocks(this);
mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock,
ctx.getCacheDir());
@@ -238,8 +238,8 @@
private static class TestUserDataPreparer extends UserDataPreparer {
File testDir;
- TestUserDataPreparer(Installer installer, Object installLock, Context context,
- File testDir) {
+ TestUserDataPreparer(Installer installer, PackageManagerTracedLock installLock,
+ Context context, File testDir) {
super(installer, installLock, context);
this.testDir = testDir;
}
diff --git a/services/tests/apexsystemservices/OWNERS b/services/tests/apexsystemservices/OWNERS
index 0295b9e..8b6675a 100644
--- a/services/tests/apexsystemservices/OWNERS
+++ b/services/tests/apexsystemservices/OWNERS
@@ -1,4 +1 @@
-omakoto@google.com
-satayev@google.com
-
include platform/packages/modules/common:/OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index a0a611f..46d08b0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -21,7 +21,6 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN;
-import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -58,6 +57,7 @@
import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.RefreshRateData;
+import com.android.server.display.config.SupportedModeData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
@@ -613,7 +613,7 @@
assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA);
assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA);
assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2);
- SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
+ SupportedModeData mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
assertEquals(mode.refreshRate, 60, SMALL_DELTA);
assertEquals(mode.vsyncRate, 65, SMALL_DELTA);
mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1);
@@ -933,6 +933,21 @@
assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
}
+ @Test
+ public void testLowPowerSupportedModesFromConfigFile() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ RefreshRateData refreshRateData = mDisplayDeviceConfig.getRefreshRateData();
+ assertNotNull(refreshRateData);
+ assertThat(refreshRateData.lowPowerSupportedModes).hasSize(2);
+ SupportedModeData supportedModeData = refreshRateData.lowPowerSupportedModes.get(0);
+ assertThat(supportedModeData.refreshRate).isEqualTo(60);
+ assertThat(supportedModeData.vsyncRate).isEqualTo(60);
+ supportedModeData = refreshRateData.lowPowerSupportedModes.get(1);
+ assertThat(supportedModeData.refreshRate).isEqualTo(60);
+ assertThat(supportedModeData.vsyncRate).isEqualTo(120);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1089,6 +1104,19 @@
+ "</proxSensor>\n";
}
+ private String getLowPowerConfig() {
+ return "<lowPowerSupportedModes>\n"
+ + " <point>\n"
+ + " <first>60</first>\n"
+ + " <second>60</second>\n"
+ + " </point>\n"
+ + " <point>\n"
+ + " <first>60</first>\n"
+ + " <second>120</second>\n"
+ + " </point>\n"
+ + "</lowPowerSupportedModes>\n";
+ }
+
private String getHdrBrightnessConfig() {
return "<hdrBrightnessConfig>\n"
+ " <brightnessMap>\n"
@@ -1620,6 +1648,7 @@
+ "</displayBrightnessPoint>\n"
+ "</blockingZoneThreshold>\n"
+ "</higherBlockingZoneConfigs>\n"
+ + getLowPowerConfig()
+ "</refreshRate>\n"
+ "<screenOffBrightnessSensorValueToLux>\n"
+ "<item>-1</item>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index ae6361b..df96712 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -46,6 +46,7 @@
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
@@ -90,6 +91,8 @@
@Mock
private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
@Mock
+ private FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+ @Mock
private Resources mResources;
@Mock
private DisplayManagerFlags mDisplayManagerFlags;
@@ -135,7 +138,7 @@
@Override
AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
- int displayId) {
+ int displayId, DisplayManagerFlags displayManagerFlags) {
return mAutomaticBrightnessStrategy;
}
@@ -155,6 +158,11 @@
AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
return mAutoBrightnessFallbackStrategy;
}
+
+ @Override
+ FallbackBrightnessStrategy getFallbackBrightnessStrategy() {
+ return mFallbackBrightnessStrategy;
+ }
};
@Rule
@@ -355,6 +363,25 @@
}
@Test
+ public void selectStrategy_selectsFallbackStrategyAsAnUltimateFallback() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(false);
+ when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+ new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+ 0.1f, false)),
+ mFallbackBrightnessStrategy);
+ }
+
+ @Test
public void selectStrategyCallsPostProcessorForAllStrategies() {
when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 3e78118..19bff56 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -18,8 +18,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +47,7 @@
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.feature.DisplayManagerFlags;
import org.junit.After;
import org.junit.Before;
@@ -64,6 +67,9 @@
@Mock
private AutomaticBrightnessController mAutomaticBrightnessController;
+ @Mock
+ private DisplayManagerFlags mDisplayManagerFlags;
+
private BrightnessConfiguration mBrightnessConfiguration;
private float mDefaultScreenAutoBrightnessAdjustment;
private Context mContext;
@@ -80,7 +86,8 @@
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
- mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID,
+ mDisplayManagerFlags);
mBrightnessConfiguration = new BrightnessConfiguration.Builder(
new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
@@ -247,6 +254,46 @@
}
@Test
+ public void testAutoBrightnessState_modeSwitch() {
+ // Setup the test
+ when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+
+ // Validate no interaction when automaticBrightnessController is in idle mode
+ when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController, never()).switchMode(anyInt());
+
+ // Validate interaction when automaticBrightnessController is in non-idle mode, and display
+ // state is ON
+ when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT);
+
+ // Validate interaction when automaticBrightnessController is in non-idle mode, and display
+ // state is DOZE
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
+ allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+ userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController).switchMode(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE);
+ }
+
+ @Test
public void accommodateUserBrightnessChangesWorksAsExpected() {
// Verify the state if automaticBrightnessController is configured.
assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
@@ -390,7 +437,8 @@
@Test
public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
int newDisplayId = 1;
- mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId,
+ mDisplayManagerFlags);
mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
0.0f);
@@ -429,8 +477,7 @@
updateBrightness_constructsDisplayBrightnessState_withAdjustmentAutoAdjustmentFlag() {
BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
- mContext, DISPLAY_ID, displayId -> brightnessEvent);
- new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+ mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
mAutomaticBrightnessController);
float brightness = 0.4f;
@@ -461,8 +508,7 @@
updateBrightness_constructsDisplayBrightnessState_withAdjustmentTempAdjustmentFlag() {
BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
- mContext, DISPLAY_ID, displayId -> brightnessEvent);
- new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+ mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
mAutomaticBrightnessController);
float brightness = 0.4f;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
new file mode 100644
index 0000000..c4767ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class FallbackBrightnessStrategyTest {
+ private FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+
+ @Before
+ public void before() {
+ mFallbackBrightnessStrategy = new FallbackBrightnessStrategy();
+ }
+
+ @Test
+ public void updateBrightness_currentBrightnessIsSet() {
+ DisplayManagerInternal.DisplayPowerRequest
+ displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+ float currentBrightness = 0.2f;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_MANUAL);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(currentBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setSdrBrightness(currentBrightness)
+ .setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName())
+ .setShouldUpdateScreenBrightnessSetting(true)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mFallbackBrightnessStrategy.updateBrightness(
+ new StrategyExecutionRequest(displayPowerRequest, currentBrightness));
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
index a785300..27f87aa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
@@ -161,4 +161,20 @@
.isEqualTo(Integer.toString(testPropertyValue));
}
+ @Test
+ public void daltonizer_defaultValues() {
+ synchronized (mDtm.mDaltonizerModeLock) {
+ assertThat(mDtm.mDaltonizerMode).isEqualTo(-1);
+ assertThat(mDtm.mDaltonizerLevel).isEqualTo(-1);
+ }
+ }
+
+ @Test
+ public void setDaltonizerMode_newValues_valuesUpdated() {
+ mDtm.setDaltonizerMode(0, 0);
+ synchronized (mDtm.mDaltonizerModeLock) {
+ assertThat(mDtm.mDaltonizerMode).isEqualTo(0);
+ assertThat(mDtm.mDaltonizerLevel).isEqualTo(0);
+ }
+ }
}
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 cd1e9e8..714b423 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
@@ -131,7 +131,8 @@
/* defaultRefreshRate= */ 0,
/* defaultPeakRefreshRate= */ 0,
/* defaultRefreshRateInHbmHdr= */ 0,
- /* defaultRefreshRateInHbmSunlight= */ 0);
+ /* defaultRefreshRateInHbmSunlight= */ 0,
+ /* lowPowerSupportedModes =*/ List.of());
public static Collection<Object[]> getAppRequestedSizeTestCases() {
var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
@@ -157,7 +158,7 @@
APP_MODE_HIGH_90.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
{/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
@@ -169,7 +170,7 @@
APP_MODE_65.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
{/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
@@ -181,7 +182,7 @@
APP_MODE_65.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSizeAndPhysicalRefreshRatesRange(
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
@@ -197,7 +198,7 @@
APP_MODE_65.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSizeAndPhysicalRefreshRatesRange(
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
@@ -213,7 +214,7 @@
APP_MODE_HIGH_90.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSizeAndPhysicalRefreshRatesRange(
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
@@ -229,7 +230,7 @@
APP_MODE_HIGH_90.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forSizeAndPhysicalRefreshRatesRange(
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
@@ -245,7 +246,7 @@
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
- Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
Vote.forPhysicalRefreshRates(
0, 64.99f))}});
@@ -598,7 +599,7 @@
< Vote.PRIORITY_APP_REQUEST_SIZE);
assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
- > Vote.PRIORITY_LOW_POWER_MODE);
+ > Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE);
Display.Mode[] modes = new Display.Mode[4];
modes[0] = new Display.Mode(
@@ -676,9 +677,9 @@
@Test
public void testLPMHasHigherPriorityThanUser() {
- assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+ assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE
> Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
- assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+ assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE
> Vote.PRIORITY_APP_REQUEST_SIZE);
Display.Mode[] modes = new Display.Mode[4];
@@ -700,7 +701,7 @@
Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
appRequestedMode.getPhysicalHeight()));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(2);
@@ -715,7 +716,7 @@
Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
appRequestedMode.getPhysicalHeight()));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(4);
@@ -730,7 +731,7 @@
Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
appRequestedMode.getPhysicalHeight()));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(2);
@@ -745,7 +746,7 @@
Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
appRequestedMode.getPhysicalHeight()));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.baseModeId).isEqualTo(4);
@@ -906,7 +907,7 @@
Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(60, 60));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
@@ -946,7 +947,7 @@
votesByDisplay.put(DISPLAY_ID, votes);
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(30, 90));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
assertThat(director.getModeSwitchingType())
@@ -987,7 +988,7 @@
votesByDisplay.put(DISPLAY_ID, votes);
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(30, 90));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
assertThat(director.getModeSwitchingType())
@@ -1029,7 +1030,7 @@
votesByDisplay.put(DISPLAY_ID, votes);
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(30, 90));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
assertThat(director.getModeSwitchingType())
@@ -1900,7 +1901,7 @@
director.start(createMockSensorManager());
SparseArray<Vote> votes = new SparseArray<>();
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 50f));
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(DISPLAY_ID_2, votes);
@@ -2298,7 +2299,7 @@
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50);
@@ -2311,7 +2312,7 @@
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90));
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80);
assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80);
@@ -2323,7 +2324,7 @@
votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90));
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
@@ -2343,7 +2344,7 @@
votesByDisplay.put(DISPLAY_ID, votes);
votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(70));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2360,7 +2361,7 @@
votes.clear();
votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(55));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2374,7 +2375,7 @@
Vote.forRenderFrameRates(0, 52));
votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(55));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2392,7 +2393,7 @@
Vote.forRenderFrameRates(0, 58));
votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(55));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2521,7 +2522,7 @@
votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120));
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120);
@@ -2542,7 +2543,7 @@
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(DISPLAY_ID, votes);
- votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
Vote.forRenderFrameRates(0, 30));
director.injectVotesByDisplay(votesByDisplay);
@@ -3168,7 +3169,8 @@
/* defaultRefreshRate= */ 60,
/* defaultPeakRefreshRate= */ 65,
/* defaultRefreshRateInHbmHdr= */ 65,
- /* defaultRefreshRateInHbmSunlight= */ 75);
+ /* defaultRefreshRateInHbmSunlight= */ 75,
+ /* lowPowerSupportedModes= */ List.of());
when(displayDeviceConfig.getRefreshRateData()).thenReturn(refreshRateData);
when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
@@ -3390,9 +3392,10 @@
ArgumentCaptor<DisplayListener> displayListenerCaptor =
ArgumentCaptor.forClass(DisplayListener.class);
- verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class));
- DisplayListener displayListener = displayListenerCaptor.getValue();
+ // DisplayObserver should register first
+ DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0);
float refreshRate = 60;
mInjector.mDisplayInfo.layoutLimitedRefreshRate =
@@ -3417,9 +3420,10 @@
ArgumentCaptor<DisplayListener> displayListenerCaptor =
ArgumentCaptor.forClass(DisplayListener.class);
- verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(),
any(Handler.class));
- DisplayListener displayListener = displayListenerCaptor.getValue();
+ // DisplayObserver should register first
+ DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0);
mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
mInjector.mDisplayInfoValid = false;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 2d317af..ee79d19 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -407,7 +407,8 @@
assertThat(mObserver).isNull();
mObserver = invocation.getArgument(0);
return null;
- }).when(mInjector).registerDisplayListener(any(), any());
+ }).when(mInjector).registerDisplayListener(
+ any(DisplayModeDirector.DisplayObserver.class), any());
doAnswer(c -> {
DisplayInfo info = c.getArgument(1);
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 4d910ce..e431c8c 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
@@ -27,8 +27,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.test.FakeSettingsProvider
import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.config.RefreshRateData
+import com.android.server.display.config.SupportedModeData
import com.android.server.display.feature.DisplayManagerFlags
import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
+import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates
import com.android.server.testutils.TestHandler
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -69,6 +72,13 @@
private val RANGES_MIN60_60TO90 = RefreshRateRanges(RANGE_60_INF, RANGE_60_90)
private val RANGES_MIN90_90TO90 = RefreshRateRanges(RANGE_90_INF, RANGE_90_90)
+private val LOW_POWER_GLOBAL_VOTE = Vote.forRenderFrameRates(0f, 60f)
+private val LOW_POWER_REFRESH_RATE_DATA = createRefreshRateData(
+ lowPowerSupportedModes = listOf(SupportedModeData(60f, 60f), SupportedModeData(60f, 240f)))
+private val LOW_POWER_EMPTY_REFRESH_RATE_DATA = createRefreshRateData()
+private val EXPECTED_SUPPORTED_MODES_VOTE = SupportedRefreshRatesVote(
+ listOf(RefreshRates(60f, 60f), RefreshRates(60f, 240f)))
+
@SmallTest
@RunWith(TestParameterInjector::class)
class SettingsObserverTest {
@@ -103,7 +113,7 @@
val displayModeDirector = DisplayModeDirector(
spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
val ddcByDisplay = SparseArray<DisplayDeviceConfig>()
- whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
+ whenever(mockDeviceConfig.refreshRateData).thenReturn(testCase.refreshRateData)
ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig)
displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay)
val settingsObserver = displayModeDirector.SettingsObserver(
@@ -113,27 +123,30 @@
false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1)
assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID,
- Vote.PRIORITY_LOW_POWER_MODE)).isEqualTo(testCase.expectedVote)
+ Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE)).isEqualTo(testCase.globalVote)
+ assertThat(displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_LOW_POWER_MODE_MODES)).isEqualTo(testCase.displayVote)
}
enum class LowPowerTestCase(
- val vrrSupported: Boolean,
+ val refreshRateData: RefreshRateData,
val vsyncLowPowerVoteEnabled: Boolean,
val lowPowerModeEnabled: Boolean,
- internal val expectedVote: Vote?
+ internal val globalVote: Vote?,
+ internal val displayVote: Vote?
) {
- ALL_ENABLED(true, true, true,
- SupportedRefreshRatesVote(listOf(
- SupportedRefreshRatesVote.RefreshRates(60f, 240f),
- SupportedRefreshRatesVote.RefreshRates(60f, 60f)
- ))),
- LOW_POWER_OFF(true, true, false, null),
- DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true,
- RefreshRateVote.RenderVote(0f, 60f)),
- DVRR_NOT_SUPPORTED_LOW_POWER_OFF(false, true, false, null),
- VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(true, false, true,
- RefreshRateVote.RenderVote(0f, 60f)),
- VSYNC_VOTE_DISABLED_LOW_POWER_OFF(true, false, false, null),
+ ALL_ENABLED(LOW_POWER_REFRESH_RATE_DATA, true, true,
+ LOW_POWER_GLOBAL_VOTE, EXPECTED_SUPPORTED_MODES_VOTE),
+ LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, true, false,
+ null, null),
+ EMPTY_REFRESH_LOW_POWER_ON(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, true,
+ LOW_POWER_GLOBAL_VOTE, null),
+ EMPTY_REFRESH__LOW_POWER_OFF(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, false,
+ null, null),
+ VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(LOW_POWER_REFRESH_RATE_DATA, false, true,
+ LOW_POWER_GLOBAL_VOTE, null),
+ VSYNC_VOTE_DISABLED_LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, false, false,
+ null, null),
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 6b90bde..1206e30 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -16,6 +16,9 @@
package com.android.server.display.mode
+import com.android.server.display.config.RefreshRateData
+import com.android.server.display.config.SupportedModeData
+
internal fun createVotesSummary(
isDisplayResolutionRangeVotingEnabled: Boolean = true,
supportedModesVoteEnabled: Boolean = true,
@@ -24,4 +27,16 @@
): VoteSummary {
return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled,
loggingEnabled, supportsFrameRateOverride)
-}
\ No newline at end of file
+}
+
+fun createRefreshRateData(
+ defaultRefreshRate: Int = 60,
+ defaultPeakRefreshRate: Int = 60,
+ defaultRefreshRateInHbmHdr: Int = 60,
+ defaultRefreshRateInHbmSunlight: Int = 60,
+ lowPowerSupportedModes: List<SupportedModeData> = emptyList()
+): RefreshRateData {
+ return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
+ defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+ lowPowerSupportedModes)
+}
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
index 88ab871..874e991 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
@@ -273,28 +273,36 @@
}
@Test
- public void setDreamHasFocus_true_dreamHasFocus() {
+ public void setDreamIsObscured_true_dreamIsNotFrontmost() {
mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
- mDreamController.setDreamHasFocus(true);
- assertTrue(mDreamController.dreamHasFocus());
+ mDreamController.setDreamIsObscured(true);
+ assertFalse(mDreamController.dreamIsFrontmost());
}
@Test
- public void setDreamHasFocus_false_dreamDoesNotHaveFocus() {
+ public void setDreamIsObscured_false_dreamIsFrontmost() {
mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
- mDreamController.setDreamHasFocus(false);
- assertFalse(mDreamController.dreamHasFocus());
+ mDreamController.setDreamIsObscured(false);
+ assertTrue(mDreamController.dreamIsFrontmost());
}
@Test
- public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() {
- mDreamController.setDreamHasFocus(true);
- // Dream still doesn't have focus because it was never started.
- assertFalse(mDreamController.dreamHasFocus());
+ public void setDreamIsObscured_notDreaming_dreamIsNotFrontmost() {
+ mDreamController.setDreamIsObscured(true);
+ // Dream still isn't frontmost because it was never started.
+ assertFalse(mDreamController.dreamIsFrontmost());
+ }
+
+ @Test
+ public void startDream_dreamIsFrontmost() {
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ assertTrue(mDreamController.dreamIsFrontmost());
}
private ServiceConnection captureServiceConnection() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c9aab53..396edae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -186,7 +186,7 @@
class Mocks {
val lock = PackageManagerTracedLock()
- val installLock = Any()
+ val installLock = PackageManagerTracedLock()
val injector: PackageManagerServiceInjector = mock()
val systemWrapper: PackageManagerServiceInjector.SystemWrapper = mock()
val context: Context = mock()
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
index d29bf1a..3635e9a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.BatteryManager;
@@ -49,9 +50,9 @@
private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
private MockClock mMockClock;
+ private BatteryStatsImpl.BatteryStatsConfig mConfig;
private MockBatteryStatsImpl mBatteryStatsImpl;
-
/**
* Battery status. Must be one of the following:
* {@link BatteryManager#BATTERY_STATUS_UNKNOWN}
@@ -91,8 +92,9 @@
@Before
public void setUp() throws IOException {
+ mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class);
mMockClock = new MockClock();
- mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock,
+ mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock,
Files.createTempDirectory("BatteryStatsResetTest").toFile());
mBatteryStatsImpl.onSystemReady(mock(Context.class));
@@ -110,10 +112,7 @@
@Test
public void testResetOnUnplug_highBatteryLevel() {
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(true)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true);
long expectedResetTimeUs = 0;
@@ -137,10 +136,7 @@
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable high battery level reset on unplug.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(false)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
dischargeToLevel(60);
@@ -153,10 +149,7 @@
@Test
public void testResetOnUnplug_significantCharge() {
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugAfterSignificantCharge(true)
- .build());
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true);
long expectedResetTimeUs = 0;
unplugBattery();
@@ -186,10 +179,7 @@
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable reset on unplug after significant charge.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugAfterSignificantCharge(false)
- .build());
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
// Battery level dropped below 20%.
dischargeToLevel(15);
@@ -256,11 +246,9 @@
@Test
public void testResetWhilePluggedIn_longPlugIn() {
// disable high battery level reset on unplug.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(false)
- .setResetOnUnplugAfterSignificantCharge(false)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
+
long expectedResetTimeUs = 0;
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 2d7cb22..6edfede 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -98,10 +98,12 @@
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
- 10000)
- .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- 10000);
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU), 10000)
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000);
}
private void initBatteryStats() {
@@ -290,7 +292,8 @@
public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent,
long throttleMs) {
- mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs);
+ mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs);
return this;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index 4e3e80f..d1105a4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -32,6 +32,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.StringWriter;
import java.util.function.IntSupplier;
@RunWith(AndroidJUnit4.class)
@@ -127,6 +129,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public int getDefaultCpuPowerBrackets() {
return mDefaultCpuPowerBrackets;
}
@@ -363,6 +370,36 @@
.isEqualTo(528);
}
+ @Test
+ public void dump() {
+ mockCpuScalingPolicies(1);
+ mockPowerProfile();
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+ collector.collectStats(); // Establish baseline
+
+ mockKernelCpuStats(new long[]{1111, 2222, 3333},
+ new SparseArray<>() {{
+ put(UID_1, new long[]{100, 200});
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{200, 450});
+ }}, 0, 1234);
+
+ PowerStats powerStats = collector.collectStats();
+
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+ powerStats.dump(pw);
+ pw.flush();
+ String dump = sw.toString();
+
+ assertThat(dump).contains("duration=1234");
+ assertThat(dump).contains("steps: [1111, 2222, 3333]");
+ assertThat(dump).contains("UID 42: time: [100, 200]");
+ assertThat(dump).contains("UID 99: time: [300, 600]");
+ }
+
private void mockCpuScalingPolicies(int clusterCount) {
SparseArray<int[]> cpus = new SparseArray<>();
SparseArray<int[]> freqs = new SparseArray<>();
@@ -386,8 +423,8 @@
private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
int defaultCpuPowerBracketsPerEnergyConsumer) {
CpuPowerStatsCollector collector = new CpuPowerStatsCollector(
- new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer),
- 0);
+ new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer)
+ );
collector.addConsumer(stats -> mCollectedStats = stats);
collector.setEnabled(true);
return collector;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 70c40f5..644ae47 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -46,7 +47,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@@ -92,9 +95,10 @@
long duration = 0;
long[] stats = null;
- String[] cpuStatsDump = dumpCpuStats();
+ List<String> cpuStatsDump = dumpCpuStats();
Pattern durationPattern = Pattern.compile("duration=([0-9]*)");
- Pattern uidPattern = Pattern.compile("UID " + mTestPkgUid + ": \\[([0-9,\\s]*)]");
+ Pattern uidPattern = Pattern.compile(
+ "UID " + UserHandle.formatUid(mTestPkgUid) + ": time: [\\[]?([0-9,\\s]*)[]]?");
for (String line : cpuStatsDump) {
Matcher durationMatcher = durationPattern.matcher(line);
if (durationMatcher.find()) {
@@ -119,15 +123,23 @@
assertThat(total).isAtLeast((long) (WORK_DURATION_MS * 0.8));
}
- private String[] dumpCpuStats() throws Exception {
+ private List<String> dumpCpuStats() throws Exception {
+ ArrayList<String> cpuStats = new ArrayList<>();
String dump = executeCmdSilent("dumpsys batterystats --sample");
String[] lines = dump.split("\n");
+ boolean inCpuSection = false;
for (int i = 0; i < lines.length; i++) {
- if (lines[i].startsWith("CpuPowerStatsCollector")) {
- return Arrays.copyOfRange(lines, i + 1, lines.length);
+ if (!inCpuSection) {
+ if (lines[i].startsWith("CpuPowerStatsCollector")) {
+ inCpuSection = true;
+ }
+ } else if (lines[i].startsWith(" ")) {
+ cpuStats.add(lines[i]);
+ } else {
+ break;
}
}
- return new String[0];
+ return cpuStats;
}
private void doSomeWork() throws Exception {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index f93c4da..0275319 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -123,6 +123,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -337,16 +342,18 @@
pw.flush();
String dump = sw.toString();
assertThat(dump).contains("duration=100");
+ assertThat(dump).contains("sleep: 200 idle: 300 scan: 60000 call: 40000 energy: "
+ + ((64321 - 10000) * 1000 / 3500));
+ assertThat(dump).contains("(LTE) rx: 7000 tx: [8000, 9000, 1000, 2000, 3000]");
+ assertThat(dump).contains("(NR MMWAVE) rx: 6000 tx: [1000, 2000, 3000, 4000, 5000]");
assertThat(dump).contains(
- "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]");
- assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]");
- assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]");
- assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]");
- assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]");
+ "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000");
+ assertThat(dump).contains(
+ "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000");
}
private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index 4ac7ad8..29ef3b6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -114,6 +114,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -186,7 +191,7 @@
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
@@ -305,79 +310,9 @@
}
@Test
- public void measuredEnergyModel() {
- // PowerStats hardware is available
- when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
- .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
-
- mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
- .initMeasuredEnergyStatsLocked();
-
- MobileRadioPowerStatsProcessor processor =
- new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
-
- AggregatedPowerStatsConfig.PowerComponent config =
- new AggregatedPowerStatsConfig.PowerComponent(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
- .trackDeviceStates(STATE_POWER, STATE_SCREEN)
- .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
- .setProcessor(processor);
-
+ public void energyConsumerModel() {
PowerComponentAggregatedPowerStats aggregatedStats =
- new PowerComponentAggregatedPowerStats(
- new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
-
- aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
- aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
- aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
-
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
- collector.setEnabled(true);
-
- // Initial empty ModemActivityInfo.
- mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
-
- when(mConsumedEnergyRetriever.getConsumedEnergyUws(
- new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
- .thenReturn(new long[]{0});
-
- // Establish a baseline
- aggregatedStats.addPowerStats(collector.collectStats(), 0);
-
- // Turn the screen off after 2.5 seconds
- aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
- 5000);
-
- // Note application network activity
- NetworkStats networkStats = mockNetworkStats(10000, 1,
- mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
- mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
-
- when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
-
- ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
- new int[]{100, 200, 300, 400, 500}, 600);
- mockModemActivityInfo(mai);
-
- mStatsRule.setTime(10_000, 10_000);
-
- long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
- when(mConsumedEnergyRetriever.getConsumedEnergyUws(
- new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
-
- when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
- when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
-
- PowerStats powerStats = collector.collectStats();
-
- aggregatedStats.addPowerStats(powerStats, 10_000);
-
- processor.finish(aggregatedStats);
+ prepareAggregatedStats_energyConsumerModel();
MobileRadioPowerStatsLayout statsLayout =
new MobileRadioPowerStatsLayout(
@@ -448,6 +383,102 @@
.isWithin(PRECISION).of(0.75);
}
+ @Test
+ public void test_toString() {
+ PowerComponentAggregatedPowerStats stats = prepareAggregatedStats_energyConsumerModel();
+ String string = stats.toString();
+ assertThat(string).contains("(pwr-other scr-on)"
+ + " sleep: 500 idle: 750 scan: 1388 call: 50 energy: 2500000 power: 0.672");
+ assertThat(string).contains("(pwr-other scr-other)"
+ + " sleep: 1500 idle: 2250 scan: 4166 call: 150 energy: 7500000 power: 2.02");
+ assertThat(string).contains("(pwr-other scr-on other)"
+ + " rx: 150 tx: [25, 50, 75, 100, 125]");
+ assertThat(string).contains("(pwr-other scr-other other)"
+ + " rx: 450 tx: [75, 150, 225, 300, 375]");
+ assertThat(string).contains("(pwr-other scr-on fg)"
+ + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+ assertThat(string).contains("(pwr-other scr-other bg)"
+ + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+ assertThat(string).contains("(pwr-other scr-other fgs)"
+ + " rx-pkts: 750 rx-B: 5000 tx-pkts: 150 tx-B: 10000 power: 0.396");
+ }
+
+ private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() {
+ // PowerStats hardware is available
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+ .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
+
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
+ .initMeasuredEnergyStatsLocked();
+
+ MobileRadioPowerStatsProcessor processor =
+ new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ // Initial empty ModemActivityInfo.
+ mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{0});
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ mockModemActivityInfo(mai);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+
+ when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
+
+ PowerStats powerStats = collector.collectStats();
+
+ aggregatedStats.addPowerStats(powerStats, 10_000);
+
+ processor.finish(aggregatedStats);
+ return aggregatedStats;
+ }
+
private int[] states(int... states) {
return states;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1d48975..2c03f9d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -72,6 +72,11 @@
this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver());
}
+ MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) {
+ this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()),
+ new PowerStatsUidResolver());
+ }
+
MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
@@ -137,13 +142,6 @@
return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
}
- public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) {
- synchronized (this) {
- mBatteryStatsConfig = config;
- }
- return this;
- }
-
public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
mNetworkStats = networkStats;
return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 1b045c5..ae258cd3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -29,8 +29,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@@ -165,7 +163,7 @@
}
@Test
- public void dump() {
+ public void test_toString() {
MultiStateStats.Factory factory = makeFactory(true, true, false);
MultiStateStats multiStateStats = factory.create();
multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000);
@@ -175,13 +173,10 @@
multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000);
multiStateStats.increment(new long[]{100, 200}, 5000);
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw, true);
- multiStateStats.dump(pw, Arrays::toString);
- assertThat(sw.toString()).isEqualTo(
- "plugged-in fg [25, 50]\n"
- + "on-battery fg [25, 50]\n"
- + "on-battery bg [50, 100]\n"
+ assertThat(multiStateStats.toString()).isEqualTo(
+ "(plugged-in fg) [25, 50]\n"
+ + "(on-battery fg) [25, 50]\n"
+ + "(on-battery bg) [50, 100]"
);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index dadcf3f..69d655b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -98,6 +98,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -175,7 +180,7 @@
aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index 8b1d423..a280cfe 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -138,6 +138,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -304,16 +309,20 @@
String dump = sw.toString();
assertThat(dump).contains("duration=7500");
assertThat(dump).contains(
- "stats=[6000, 1000, 300, 200, 634, 945, " + ((64321 - 10000) * 1000 / 3500)
- + ", 0, 0]");
- assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 400, 600, 0]");
- assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 234, 345, 0]");
+ "rx: 6000 tx: 1000 idle: 300 scan: 200 basic-scan: 634 batched-scan: 945"
+ + " energy: " + ((64321 - 10000) * 1000 / 3500));
+ assertThat(dump).contains(
+ "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000"
+ + " scan: 400 batched-scan: 600");
+ assertThat(dump).contains(
+ "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"
+ + " scan: 234 batched-scan: 345");
}
private PowerStats collectPowerStats(boolean hasPowerReporting) {
when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index 257a1a6..3ceaf35 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -142,6 +142,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -195,7 +200,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -307,7 +312,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -420,7 +425,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Establish a baseline
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 27c522d..b56af87 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -25,6 +25,13 @@
value="/data/local/tmp/cts/content/broken_shortcut.xml" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="set-global-setting" key="verifier_engprod" value="1" />
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3dc375c..9cd3186 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -368,24 +368,32 @@
}
@Test
- public void testAuthenticate_throwsWhenUsingTestConfigurations() {
+ public void testAuthenticate_throwsWhenUsingTestApis() {
final PromptInfo promptInfo = mock(PromptInfo.class);
- when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false);
- when(promptInfo.containsTestConfigurations()).thenReturn(true);
+ when(promptInfo.requiresInternalPermission()).thenReturn(false);
+ when(promptInfo.requiresTestOrInternalPermission()).thenReturn(true);
- testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ testAuthenticate_throwsSecurityException(promptInfo);
}
@Test
public void testAuthenticate_throwsWhenUsingPrivateApis() {
final PromptInfo promptInfo = mock(PromptInfo.class);
- when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true);
- when(promptInfo.containsTestConfigurations()).thenReturn(false);
+ when(promptInfo.requiresInternalPermission()).thenReturn(true);
+ when(promptInfo.requiresTestOrInternalPermission()).thenReturn(false);
- testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ testAuthenticate_throwsSecurityException(promptInfo);
}
- private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) {
+ @Test
+ public void testAuthenticate_throwsWhenUsingAdvancedApis() {
+ final PromptInfo promptInfo = mock(PromptInfo.class);
+ when(promptInfo.requiresAdvancedPermission()).thenReturn(true);
+
+ testAuthenticate_throwsSecurityException(promptInfo);
+ }
+
+ private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index be5e262..c1ae852 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -24,8 +24,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -72,6 +74,11 @@
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 5be3c8e..a5f7bb1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -22,8 +22,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
import android.os.test.TestLooper;
@@ -84,6 +86,11 @@
protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 7845c30..857ee1a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -22,8 +22,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
@@ -90,6 +92,11 @@
protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index 98789ac..6ace9f1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -33,8 +33,10 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -140,17 +142,8 @@
// do nothing
}
- /**
- * Override displayOsd to prevent it from broadcasting an intent, which
- * can trigger a SecurityException.
- */
@Override
- void displayOsd(int messageId) {
- // do nothing
- }
-
- @Override
- void displayOsd(int messageId, int extra) {
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
// do nothing
}
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 9b65762..2dd593c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -18,6 +18,8 @@
import static org.junit.Assert.assertEquals;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
@@ -103,6 +105,11 @@
protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiCecLocalDeviceAudioSystem =
new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 922706e..e669e7c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -25,8 +25,10 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -90,6 +92,11 @@
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 68ef80f..29d20a6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -30,7 +30,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -123,6 +125,11 @@
boolean isPowerStandby() {
return false;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 26b448a..d32b75b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -29,7 +29,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -132,6 +134,11 @@
boolean isPowerStandby() {
return false;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index a621055..c7574bd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -98,6 +99,8 @@
audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
+ doNothing().when(mHdmiControlServiceSpy)
+ .sendBroadcastAsUser(any(Intent.class));
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
index 30ce961..e1e101f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -19,6 +19,7 @@
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -30,6 +31,7 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -90,6 +92,8 @@
audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
+ doNothing().when(mHdmiControlServiceSpy)
+ .sendBroadcastAsUser(any(Intent.class));
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 0870bac..7ed596e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -48,6 +48,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
@@ -110,6 +111,8 @@
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
+ doNothing().when(mHdmiControlServiceSpy)
+ .sendBroadcastAsUser(any(Intent.class));
mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
mNativeWrapper = new FakeNativeWrapper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 5520897..5502de8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -26,7 +26,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -114,6 +116,11 @@
return defVal;
}
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.getHdmiCecConfig().setIntValue(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 28da97c..8df7d54 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -28,7 +28,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -138,6 +140,11 @@
boolean canGoToStandby() {
return true;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 3dd8312..192be2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -37,7 +37,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.Result;
@@ -169,6 +171,11 @@
void wakeUp() {
mWakeupMessageReceived = true;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 4faeea5..b50684b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -26,6 +26,8 @@
import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
+import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -41,7 +43,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -184,17 +188,8 @@
return mEarcBlocksArc;
}
- /**
- * Override displayOsd to prevent it from broadcasting an intent, which
- * can trigger a SecurityException.
- */
@Override
- void displayOsd(int messageId) {
- // do nothing
- }
-
- @Override
- void displayOsd(int messageId, int extra) {
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
// do nothing
}
};
@@ -1787,9 +1782,17 @@
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1798,6 +1801,10 @@
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
+ // Assume there was a retry and the action did not finish earlier.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
}
@@ -1807,9 +1814,18 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1834,8 +1850,18 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1854,8 +1880,16 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
.setLogicalAddress(ADDR_PLAYBACK_1)
.setPhysicalAddress(0x1000)
@@ -1869,6 +1903,10 @@
mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
mTestLooper.dispatchAll();
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
mNativeWrapper.clearResultMessages();
mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
@@ -1881,6 +1919,41 @@
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
}
+ @Test
+ public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage setStreamPathFromTv =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Even if the device at the end of this path doesn't answer to this message, TV should not
+ // continue the RequestActiveSourceAction.
+ mHdmiControlService.sendCecCommand(setStreamPathFromTv);
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Assume there was a retry and the action did not finish earlier.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
@Test
public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
@@ -1907,7 +1980,12 @@
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // TV will send <Active Source> when it selects its internal source.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
@@ -1937,6 +2015,8 @@
public void handleStandby_fromActiveSource_standby() {
mPowerManager.setInteractive(true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
"HdmiCecLocalDeviceTvTest");
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index c002067..9412ee0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -21,8 +21,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -88,6 +90,11 @@
boolean isPowerStandby() {
return false;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index e1b66b5..126a658 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -52,6 +52,7 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -121,6 +122,8 @@
audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
+ doNothing().when(mHdmiControlServiceSpy)
+ .sendBroadcastAsUser(any(Intent.class));
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 4641802..298ff46 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -26,8 +26,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -104,6 +106,11 @@
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 9f0a44c..1d4a72f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -25,8 +25,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -78,6 +80,11 @@
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 043db1e..cafe1e7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -21,7 +21,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.os.Looper;
@@ -115,6 +117,11 @@
boolean isPowerStandbyOrTransient() {
return false;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
index 061e1f9..864a182 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
@@ -21,7 +21,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
@@ -72,6 +74,11 @@
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mIsPowerStandby = false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
index b25ea2c..06709cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -21,7 +21,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
@@ -75,6 +77,11 @@
boolean verifyPhysicalAddresses(HdmiCecMessage message) {
return true;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index f608c235..5163e29 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -29,7 +29,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -177,6 +179,11 @@
protected HdmiCecConfig getHdmiCecConfig() {
return hdmiCecConfig;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a73f4aa..e4297ef 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -24,12 +24,14 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -87,6 +89,8 @@
audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
+ doNothing().when(mHdmiControlServiceSpy)
+ .sendBroadcastAsUser(any(Intent.class));
mLooper = mTestLooper.getLooper();
mHdmiControlServiceSpy.setIoLooper(mLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 02bed22..4dcc6a4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -25,8 +25,10 @@
import static org.mockito.Mockito.spy;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.os.Looper;
@@ -82,17 +84,8 @@
// do nothing
}
- /**
- * Override displayOsd to prevent it from broadcasting an intent, which
- * can trigger a SecurityException.
- */
@Override
- void displayOsd(int messageId) {
- // do nothing
- }
-
- @Override
- void displayOsd(int messageId, int extra) {
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
// do nothing
}
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index df27e78..4aa074b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.assertTrue;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
@@ -143,6 +145,11 @@
protected boolean isStandbyMessageReceived() {
return mStandbyMessageReceived;
}
+
+ @Override
+ protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+ // do nothing
+ }
};
Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 07fb9fc..570256b 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -19,9 +19,16 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.util.DebugUtils.valueToString;
import static org.junit.Assert.assertEquals;
@@ -51,7 +58,10 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
@@ -62,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -84,6 +95,9 @@
@Mock private IBatteryStats.Stub mBatteryStatsService;
@Mock private INetd.Stub mNetdService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private static final int TEST_UID = 111;
@NonNull
@@ -254,6 +268,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
public void testMeteredNetworkRestrictions() throws RemoteException {
// Make sure the mocked netd method returns true.
doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
@@ -295,6 +310,69 @@
}
@Test
+ @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+ public void testMeteredNetworkRestrictionsByAdminChain() {
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+ FIREWALL_RULE_DENY);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+ FIREWALL_RULE_DENY);
+ assertTrue("Should be true since mobile data usage is restricted by admin chain",
+ mNMService.isNetworkRestricted(TEST_UID));
+
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ assertFalse("Should be false since mobile data usage is no longer restricted by admin",
+ mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+ public void testMeteredNetworkRestrictionsByUserChain() {
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+ FIREWALL_RULE_DENY);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+ FIREWALL_RULE_DENY);
+ assertTrue("Should be true since mobile data usage is restricted by user chain",
+ mNMService.isNetworkRestricted(TEST_UID));
+
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ assertFalse("Should be false since mobile data usage is no longer restricted by user",
+ mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+ public void testDataSaverRestrictionsWithAllowChain() {
+ mNMService.setDataSaverModeEnabled(true);
+ verify(mCm).setDataSaverEnabled(true);
+
+ assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW);
+ assertFalse("Should be false since data saver is on and the uid is allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+
+ // remove uid from allowlist and turn datasaver off again
+
+ mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID,
+ FIREWALL_RULE_DEFAULT);
+ mNMService.setDataSaverModeEnabled(false);
+ verify(mCm).setDataSaverEnabled(false);
+
+ assertFalse("Network should not be restricted when data saver is off",
+ mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
public void testFirewallChains() {
final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
// Dozable chain
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 9fc46c5..2f3bca0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -1786,10 +1786,10 @@
/* matchesInterruptionFilter= */ false,
/* visibilityOverride= */ 0,
/* suppressedVisualEffects= */ 0,
- mParentNotificationChannel.getImportance(),
+ mNotificationChannel.getImportance(),
/* explanation= */ null,
/* overrideGroupKey= */ null,
- mParentNotificationChannel,
+ mNotificationChannel,
/* overridePeople= */ null,
/* snoozeCriteria= */ null,
/* showBadge= */ 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 4af20a9..70a0038 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -24,6 +24,8 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
@@ -52,6 +54,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -67,6 +70,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
@@ -93,6 +97,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
@@ -188,6 +193,8 @@
getContext().addMockSystemService(Vibrator.class, mVibrator);
getContext().addMockSystemService(PackageManager.class, mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ anyString())).thenReturn(PERMISSION_DENIED);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -210,6 +217,16 @@
verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt());
assertTrue(mAccessibilityManager.isEnabled());
+ // Enable LED pulse setting by default
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 1);
+
+ Resources resources = spy(getContext().getResources());
+ when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true);
+ when(resources.getBoolean(
+ com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true);
+ when(getContext().getResources()).thenReturn(resources);
+
// TODO (b/291907312): remove feature flag
// Disable feature flags by default. Tests should enable as needed.
mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
@@ -239,7 +256,6 @@
mAttentionHelper.setKeyguardManager(mKeyguardManager);
mAttentionHelper.setScreenOn(false);
mAttentionHelper.setInCallStateOffHook(false);
- mAttentionHelper.mNotificationPulseEnabled = true;
if (Flags.crossAppPoliteNotifications()) {
// Capture BroadcastReceiver for avalanche triggers
@@ -611,6 +627,14 @@
verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
}
+ private void verifyAttentionLights() {
+ verify(mLight, times(1)).pulse();
+ }
+
+ private void verifyNeverAttentionLights() {
+ verify(mLight, never()).pulse();
+ }
+
//
// Tests
//
@@ -1524,7 +1548,10 @@
@Test
public void testLightsLightsOffGlobally() {
- mAttentionHelper.mNotificationPulseEnabled = false;
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 0);
+ initAttentionHelper(mTestFlagResolver);
+
NotificationRecord r = getLightsNotification();
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
verifyNeverLights();
@@ -1533,6 +1560,44 @@
}
@Test
+ public void testLightsLightsResConfigDisabled() {
+ Resources resources = spy(getContext().getResources());
+ when(resources.getBoolean(
+ com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(false);
+ when(getContext().getResources()).thenReturn(resources);
+ initAttentionHelper(mTestFlagResolver);
+
+ NotificationRecord r = getLightsNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverLights();
+ assertFalse(r.isInterruptive());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testLightsUseAttentionLight() {
+ NotificationRecord r = getLightsNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyAttentionLights();
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testLightsUseAttentionLightDisabled() {
+ Resources resources = spy(getContext().getResources());
+ when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(false);
+ when(resources.getBoolean(
+ com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true);
+ when(getContext().getResources()).thenReturn(resources);
+ initAttentionHelper(mTestFlagResolver);
+
+ NotificationRecord r = getLightsNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverAttentionLights();
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testLightsDndIntercepted() {
NotificationRecord r = getLightsNotification();
r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS);
@@ -2303,6 +2368,72 @@
}
@Test
+ public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 100% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
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 2d672b8..e564ba6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -99,6 +99,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
@@ -112,6 +113,7 @@
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
@@ -339,6 +341,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -909,7 +912,9 @@
}
mService.clearNotifications();
- TestableLooper.get(this).processAllMessages();
+ if (mTestableLooper != null) {
+ mTestableLooper.processAllMessages();
+ }
try {
mService.onDestroy();
@@ -920,14 +925,16 @@
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
- // Remove scheduled messages that would be processed when the test is already done, and
- // could cause issues, for example, messages that remove/cancel shown toasts (this causes
- // problematic interactions with mocks when they're no longer working as expected).
- mWorkerHandler.removeCallbacksAndMessages(null);
+ if (mWorkerHandler != null) {
+ // Remove scheduled messages that would be processed when the test is already done, and
+ // could cause issues, for example, messages that remove/cancel shown toasts (this causes
+ // problematic interactions with mocks when they're no longer working as expected).
+ mWorkerHandler.removeCallbacksAndMessages(null);
+ }
- if (TestableLooper.get(this) != null) {
+ if (mTestableLooper != null) {
// Must remove static reference to this test object to prevent leak (b/261039202)
- TestableLooper.remove(this);
+ mTestableLooper.remove(this);
}
}
@@ -1009,7 +1016,9 @@
}
public void waitForIdle() {
- mTestableLooper.processAllMessages();
+ if (mTestableLooper != null) {
+ mTestableLooper.processAllMessages();
+ }
}
private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
@@ -1302,6 +1311,106 @@
return nrSummary;
}
+ private NotificationRecord createAndPostCallStyleNotification(String packageName,
+ UserHandle userHandle, String testName) throws Exception {
+ Person person = new Person.Builder().setName("caller").build();
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setFlag(FLAG_USER_INITIATED_JOB, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
+ testName, mUid, 0, nb.build(), userHandle, null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
+ waitForIdle();
+
+ return mService.findNotificationLocked(
+ packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+ }
+
+ private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
+ throws RemoteException {
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ return mService.findNotificationLocked(
+ mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+ }
+
+ private static <T extends Parcelable> T parcelAndUnparcel(T source,
+ Parcelable.Creator<T> creator) {
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return creator.createFromParcel(parcel);
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ return PendingIntent.getActivity(mContext, 0,
+ new Intent(action).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
+ }
+
+ private void allowTestPackageToToast() throws Exception {
+ assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+ .thenReturn(false);
+ }
+
+ private boolean enqueueToast(String testPackage, ITransientNotification callback)
+ throws RemoteException {
+ return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
+ callback);
+ }
+
+ private boolean enqueueToast(INotificationManager service, String testPackage,
+ IBinder token, ITransientNotification callback) throws RemoteException {
+ return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
+ true, DEFAULT_DISPLAY);
+ }
+
+ private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+ return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+ }
+
+ private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+ int displayId) throws RemoteException {
+ return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
+ new Binder(), text, TOAST_DURATION, isUiContext, displayId,
+ /* textCallback= */ null);
+ }
+
+ private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockIsUserVisible(int displayId, boolean visible) {
+ when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+ }
+
+ private void verifyToastShownForTestPackage(String text, int displayId) {
+ verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+ eq(TOAST_DURATION), any(), eq(displayId));
+ }
+
@Test
@DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testLimitTimeOutBroadcast() {
@@ -14069,11 +14178,12 @@
public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
// Post the first version.
Notification original = generateNotificationRecord(null).getNotification();
- original.when = 111;
+ original.when = System.currentTimeMillis();
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
waitForIdle();
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when);
reset(mUsageStats);
when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14081,7 +14191,7 @@
// Post the update.
Notification update = generateNotificationRecord(null).getNotification();
- update.when = 222;
+ update.when = System.currentTimeMillis() + 111;
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
waitForIdle();
@@ -14090,18 +14200,19 @@
verify(mUsageStats, never()).registerPostedByApp(any());
verify(mUsageStats).registerUpdatedByApp(any(), any());
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
+ assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when);
}
@Test
public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
// Post the first version.
Notification original = generateNotificationRecord(null).getNotification();
- original.when = 111;
+ original.when = System.currentTimeMillis();
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
waitForIdle();
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when);
reset(mUsageStats);
when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14109,7 +14220,7 @@
// Post the update.
Notification update = generateNotificationRecord(null).getNotification();
- update.when = 222;
+ update.when = System.currentTimeMillis() + 111;
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
waitForIdle();
@@ -14118,7 +14229,8 @@
verify(mUsageStats, never()).registerPostedByApp(any());
verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when); // old
}
@Test
@@ -15483,103 +15595,126 @@
assertThat(n.getTimeoutAfter()).isEqualTo(20);
}
- private NotificationRecord createAndPostCallStyleNotification(String packageName,
- UserHandle userHandle, String testName) throws Exception {
- Person person = new Person.Builder().setName("caller").build();
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
- StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
- testName, mUid, 0, nb.build(), userHandle, null, 0);
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_oldWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis())
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mService.addEnqueuedNotification(r);
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
- waitForIdle();
-
- return mService.findNotificationLocked(
- packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isFalse();
}
- private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
- throws RemoteException {
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_mediumOldWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis())
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- return mService.findNotificationLocked(
- mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isTrue();
}
- private static <T extends Parcelable> T parcelAndUnparcel(T source,
- Parcelable.Creator<T> creator) {
- Parcel parcel = Parcel.obtain();
- source.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- return creator.createFromParcel(parcel);
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_zeroWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(0)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isTrue();
}
- private PendingIntent createPendingIntent(String action) {
- return PendingIntent.getActivity(mContext, 0,
- new Intent(action).setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
+ @Test
+ public void testClearUIJFromUninstallingPackage() throws Exception {
+ NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar");
+ mService.addNotification(r);
+
+ when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+
+ mInternalService.cancelNotification(mPkg, mPkg, mUid, 0, r.getSbn().getTag(),
+ r.getSbn().getId(), mUserId);
+
+ // no exception
}
- private void allowTestPackageToToast() throws Exception {
- assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+ @Test
+ public void testPostFromMissingPackage_throws() throws Exception {
+ NotificationRecord r =
+ generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar");
+
+ when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+
+ try {
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getSbn().getNotification(),
+ r.getSbn().getUserId());
+ fail("Allowed to post a notification for an absent package");
+ } catch (SecurityException e) {
+ // yay
+ }
+ }
+
+ @Test
+ public void testGetEffectsSuppressor_noSuppressor() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+ assertThat(mBinderService.getEffectsSuppressor()).isNull();
+ }
+
+ @Test
+ public void testGetEffectsSuppressor_suppressorSameApp() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
mService.isSystemUid = false;
mService.isSystemAppId = false;
- setToastRateIsWithinQuota(true);
- setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
- // package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
- .thenReturn(false);
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
}
- private boolean enqueueToast(String testPackage, ITransientNotification callback)
- throws RemoteException {
- return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
- callback);
+ @Test
+ public void testGetEffectsSuppressor_suppressorDiffApp() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(null);
}
- private boolean enqueueToast(INotificationManager service, String testPackage,
- IBinder token, ITransientNotification callback) throws RemoteException {
- return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
- true, DEFAULT_DISPLAY);
- }
-
- private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
- return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
- }
-
- private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
- int displayId) throws RemoteException {
- return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
- new Binder(), text, TOAST_DURATION, isUiContext, displayId,
- /* textCallback= */ null);
- }
-
- private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
- when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
- }
-
- private void mockIsUserVisible(int displayId, boolean visible) {
- when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
- }
-
- private void mockDisplayAssignedToUser(int displayId) {
- when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
- }
-
- private void verifyToastShownForTestPackage(String text, int displayId) {
- verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
- eq(TOAST_DURATION), any(), eq(displayId));
+ @Test
+ public void testGetEffectsSuppressor_suppressorDiffAppSystemCaller() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ mService.isSystemUid = true;
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 37e0818..5787780 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -24,6 +24,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -250,6 +252,7 @@
case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+ case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED:
case "android:activity.animationFinishedListener":
// KEY_ANIMATION_FINISHED_LISTENER
case "android:activity.animSpecs": // KEY_ANIM_SPECS
@@ -319,7 +322,7 @@
Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+ "Please review if the given bundle should be protected with permissions.");
}
- assertTrue(unknownKeys.isEmpty());
+ assertThat(unknownKeys).isEmpty();
}
public static class TrampolineActivity extends Activity {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
new file mode 100644
index 0000000..12ab3e1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ActivityRefresher}.
+ *
+ * <p>Build/Install/Run:
+ * atest WmTests:ActivityRefresherTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class ActivityRefresherTests extends WindowTestsBase {
+ private Handler mMockHandler;
+ private LetterboxConfiguration mLetterboxConfiguration;
+
+ private ActivityRecord mActivity;
+ private ActivityRefresher mActivityRefresher;
+
+ private ActivityRefresher.Evaluator mEvaluatorFalse =
+ (activity, newConfig, lastReportedConfig) -> false;
+
+ private ActivityRefresher.Evaluator mEvaluatorTrue =
+ (activity, newConfig, lastReportedConfig) -> true;
+
+ private final Configuration mNewConfig = new Configuration();
+ private final Configuration mOldConfig = new Configuration();
+
+ @Before
+ public void setUp() throws Exception {
+ mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+ spyOn(mLetterboxConfiguration);
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(true);
+
+ mMockHandler = mock(Handler.class);
+ when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ });
+
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshDisabled() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(false);
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
+ configureActivityAndDisplay();
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+ .thenReturn(false);
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception {
+ configureActivityAndDisplay();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorFalse);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorFalse);
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ true);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabled()
+ throws Exception {
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(false);
+ configureActivityAndDisplay();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp()
+ throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
+ @Test
+ public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ mActivityRefresher.onActivityRefreshed(mActivity);
+
+ assertActivityRefreshRequested(false);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+ assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested,
+ boolean cycleThroughStop) throws Exception {
+ verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+ .setIsRefreshRequested(true);
+
+ final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+ cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+ /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+
+ verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+ .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+ refreshCallbackItem, resumeActivityItem);
+ }
+
+ private void configureActivityAndDisplay() {
+ mActivity = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setDisplay(mDisplayContent)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ ActivityRefresherTests.class.getName()))
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build()
+ .getTopMostActivity();
+
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(
+ mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+
+ doReturn(true).when(mActivity).inFreeformWindowingMode();
+ }
+}
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 262ba8b..c76acd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -91,6 +91,7 @@
private CameraManager mMockCameraManager;
private Handler mMockHandler;
private LetterboxConfiguration mLetterboxConfiguration;
+ private ActivityRefresher mActivityRefresher;
private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
@@ -132,8 +133,9 @@
});
CameraStateMonitor cameraStateMonitor =
new CameraStateMonitor(mDisplayContent, mMockHandler);
- mDisplayRotationCompatPolicy =
- new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor);
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+ mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent,
+ cameraStateMonitor, mActivityRefresher);
// Do not show the real toast.
spyOn(mDisplayRotationCompatPolicy);
@@ -606,7 +608,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
- .setIsRefreshAfterRotationRequested(true);
+ .setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -628,7 +630,7 @@
private void callOnActivityConfigurationChanging(
ActivityRecord activity, boolean isDisplayRotationChanging) {
- mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+ mActivityRefresher.onActivityConfigurationChanging(activity,
/* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
/* newConfig */ createConfigurationWithDisplayRotation(
isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 7380aec..d8d5729 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -65,7 +65,7 @@
performSurfacePlacementAndWaitForWindowAnimator();
mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty());
- assertTrue(mImeProvider.isReadyToShowIme());
+ assertTrue(mImeProvider.isScheduledAndReadyToShowIme());
}
/**
@@ -84,13 +84,13 @@
// Schedule (without triggering) after everything is ready.
mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
- assertTrue(mImeProvider.isReadyToShowIme());
+ assertTrue(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
// Manually trigger the show.
- mImeProvider.checkShowImePostLayout();
- // No longer ready as it was already shown.
- assertFalse(mImeProvider.isReadyToShowIme());
+ mImeProvider.checkAndStartShowImePostLayout();
+ // No longer scheduled as it was already shown.
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertTrue(mImeProvider.isImeShowing());
}
@@ -104,7 +104,7 @@
// Schedule before anything is ready.
mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
- assertFalse(mImeProvider.isReadyToShowIme());
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
@@ -115,8 +115,8 @@
mDisplayContent.updateImeInputAndControlTarget(target);
// Performing surface placement picks up the show scheduled above.
performSurfacePlacementAndWaitForWindowAnimator();
- // No longer ready as it was already shown.
- assertFalse(mImeProvider.isReadyToShowIme());
+ // No longer scheduled as it was already shown.
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertTrue(mImeProvider.isImeShowing());
}
@@ -137,19 +137,19 @@
// Schedule before starting the afterPrepareSurfacesRunnable.
mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
- assertFalse(mImeProvider.isReadyToShowIme());
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
// This tries to pick up the show scheduled above, but must fail as the
// afterPrepareSurfacesRunnable was not started yet.
mDisplayContent.applySurfaceChangesTransaction();
- assertFalse(mImeProvider.isReadyToShowIme());
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
// Starting the afterPrepareSurfacesRunnable picks up the show scheduled above.
mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
- // No longer ready as it was already shown.
- assertFalse(mImeProvider.isReadyToShowIme());
+ // No longer scheduled as it was already shown.
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertTrue(mImeProvider.isImeShowing());
}
@@ -169,7 +169,7 @@
// Schedule before surface placement.
mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
- assertFalse(mImeProvider.isReadyToShowIme());
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
// Performing surface placement picks up the show scheduled above, and succeeds.
@@ -177,8 +177,8 @@
// applySurfaceChangesTransaction. Both of them try to trigger the show,
// but only the second one can succeed, as it comes after onPostLayout.
performSurfacePlacementAndWaitForWindowAnimator();
- // No longer ready as it was already shown.
- assertFalse(mImeProvider.isReadyToShowIme());
+ // No longer scheduled as it was already shown.
+ assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertTrue(mImeProvider.isImeShowing());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0e1a1af..c69faed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -353,6 +353,17 @@
}
@Test
+ public void testControlTargetChangedWhileProviderHasNoWindow() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
+ ID_STATUS_BAR, statusBars());
+ getController().onBarControlTargetChanged(app, null, null, null);
+ assertNull(getController().getControlsForDispatch(app));
+ provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+ assertNotNull(getController().getControlsForDispatch(app));
+ }
+
+ @Test
public void testTransientVisibilityOfFixedRotationState() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
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 a60d243..6b17de4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -1594,7 +1594,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+ @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
spyOn(mController);
doReturn(true).when(mController).isVerticalThinLetterboxed();
@@ -1609,7 +1609,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+ @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
spyOn(mController);
doReturn(true).when(mController).isVerticalThinLetterboxed();
@@ -1623,6 +1623,12 @@
assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
}
+ @Test
+ public void testIsLetterboxEducationEnabled() {
+ mController.isLetterboxEducationEnabled();
+ verify(mLetterboxConfiguration).getIsEducationEnabled();
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8fe45cb..76b4e005 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -99,18 +99,7 @@
import java.util.stream.Collectors;
/**
- * Subscription manager provides the mobile subscription information that are associated with the
- * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
- * and below can see all subscriptions as it does today.
- *
- * <p>For example, if we have
- * <ul>
- * <li> Subscription 1 associated with personal profile.
- * <li> Subscription 2 associated with work profile.
- * </ul>
- * Then for SDK 35+, if the caller identity is personal profile, then
- * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
- *
+ * Subscription manager provides the mobile subscription information.
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1980,17 +1969,7 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
- * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
- * and below can see all subscriptions as it does today.
- *
- * <p>For example, if we have
- * <ul>
- * <li> Subscription 1 associated with personal profile.
- * <li> Subscription 2 associated with work profile.
- * </ul>
- * Then for SDK 35+, if the caller identity is personal profile, then this will return
- * subscription 1 only and vice versa.
+ * Get the SubscriptionInfo(s) of the currently active SIM(s).
*
* <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
* {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will
@@ -2259,9 +2238,7 @@
}
/**
- * Get the active subscription count associated with the current caller user profile for
- * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
- * it does today.
+ * Get the active subscription count.
*
* @return The current number of active subscriptions.
*
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 03ba8fa..dbe4f27 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13114,39 +13114,41 @@
})
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) {
- return getServiceStateForSubscriber(getSubId(),
+ return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()),
includeLocationData != INCLUDE_LOCATION_DATA_FINE,
includeLocationData == INCLUDE_LOCATION_DATA_NONE);
}
/**
- * Returns the service state information on specified subscription. Callers require
- * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+ * Returns the service state information on specified SIM slot.
*
- * May return {@code null} when the subscription is inactive or when there was an error
+ * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
* communicating with the phone process.
+ *
+ * @param slotIndex of phone whose service state is returned
* @param renounceFineLocationAccess Set this to true if the caller would not like to receive
* location related information which will be sent if the caller already possess
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
* @param renounceCoarseLocationAccess Set this to true if the caller would not like to
* receive location related information which will be sent if the caller already possess
* {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+ * @return Service state on specified SIM slot.
*/
- private ServiceState getServiceStateForSubscriber(int subId,
- boolean renounceFineLocationAccess,
+ private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
boolean renounceCoarseLocationAccess) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess,
- renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag());
+ return service.getServiceStateForSlot(slotIndex,
+ renounceFineLocationAccess, renounceCoarseLocationAccess,
+ getOpPackageName(), getAttributionTag());
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+ Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
- "getServiceStateForSubscriber " + subId + " NPE");
+ "getServiceStateForSlot " + slotIndex + " NPE");
}
return null;
}
@@ -13161,7 +13163,35 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public ServiceState getServiceStateForSubscriber(int subId) {
- return getServiceStateForSubscriber(subId, false, false);
+ return getServiceStateForSlot(
+ SubscriptionManager.getSlotIndex(subId), false, false);
+ }
+
+ /**
+ * Returns the service state information on specified SIM slot.
+ *
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through
+ * {@link #registerTelephonyCallback}.
+ *
+ * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
+ * communicating with the phone process
+ *
+ * See {@link #getActiveModemCount()} to get the total number of slots
+ * that are active on the device.
+ *
+ * @param slotIndex of phone whose service state is returned
+ * @return ServiceState on specified SIM slot.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ })
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ public @Nullable ServiceState getServiceStateForSlot(int slotIndex) {
+ return getServiceStateForSlot(slotIndex, false, false);
}
/**
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bb5fd5..47f53f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1015,13 +1015,27 @@
* @hide
*/
public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
+ /**
+ * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+ * is the last message to emergency service provider indicating still needs help.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4;
+ /**
+ * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+ * is the last message to emergency service provider indicating no more help is needed.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
DATAGRAM_TYPE_LOCATION_SHARING,
- DATAGRAM_TYPE_KEEP_ALIVE
+ DATAGRAM_TYPE_KEEP_ALIVE,
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d4da736..65de7e4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1399,19 +1399,18 @@
oneway void requestModemActivityInfo(in ResultReceiver result);
/**
- * Get the service state on specified subscription
- * @param subId Subscription id
+ * Get the service state on specified SIM slot.
+ * @param slotIndex of phone whose service state is returned
* @param renounceFineLocationAccess Set this to true if the caller would not like to
* receive fine location related information
* @param renounceCoarseLocationAccess Set this to true if the caller would not like to
* receive coarse location related information
* @param callingPackage The package making the call
* @param callingFeatureId The feature in the package
- * @return Service state on specified subscription.
+ * @return Service state on specified SIM slot.
*/
- ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess,
- boolean renounceCoarseLocationAccess,
- String callingPackage, String callingFeatureId);
+ ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId);
/**
* Returns the URI for the per-account voicemail ringtone set in Phone settings.
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
index 6f8f008..955b43a 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
@@ -19,7 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.server.wm.flicker">
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/>
<!-- Read and write traces from external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -46,6 +46,8 @@
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<!-- Allow the test to connect to perfetto trace processor -->
<uses-permission android:name="android.permission.INTERNET"/>
+ <!-- Allow to query for the Launcher TestInfo on SDK 30+ -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
<application android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 1dc1037..82de070 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index cf4edd5..67825d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -43,6 +43,7 @@
*
* To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
*/
+@FlakyTest(bugId = 341209752)
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -168,7 +169,6 @@
}
}
- @FlakyTest(bugId = 290736037)
/** Main activity should go from fullscreen to being a split with secondary activity. */
@Test
fun mainActivityLayerGoesFromFullscreenToSplit() {
@@ -203,7 +203,6 @@
}
}
- @FlakyTest(bugId = 288591571)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index bc3696b..eed9225 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -205,7 +205,8 @@
it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
val secondaryVisibleRegion =
it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region)
+ // TODO(b/340992001): replace coverAtLeast with coverExactly
+ overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region)
}
.then()
.isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index fb92583..379b45c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -60,14 +60,16 @@
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivity(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
- tapl.goHome()
- wmHelper
- .StateSyncBuilder()
- .withAppTransitionIdle()
- .withHomeActivityVisible()
- .waitForAndVerify()
startDisplayBounds =
wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+
+ // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait.
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
transitions {
SplitScreenUtils.enterSplit(
@@ -138,10 +140,6 @@
check { "ActivityEmbeddingSplitHeight" }
.that(leftAELayerRegion.region.bounds.height())
.isEqual(rightAELayerRegion.region.bounds.height())
- check { "SystemSplitHeight" }
- .that(rightAELayerRegion.region.bounds.height())
- .isEqual(secondaryAppLayerRegion.region.bounds.height())
- // TODO(b/292283182): Remove this special case handling.
check { "ActivityEmbeddingSplitWidth" }
.that(
abs(
@@ -150,14 +148,6 @@
)
)
.isLower(2)
- check { "SystemSplitWidth" }
- .that(
- abs(
- secondaryAppLayerRegion.region.bounds.width() -
- 2 * rightAELayerRegion.region.bounds.width()
- )
- )
- .isLower(2)
}
}
@@ -170,15 +160,9 @@
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
val rightAEWindowRegion =
visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- // There's no window for the divider bar.
- val secondaryAppLayerRegion =
- visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
check { "ActivityEmbeddingSplitHeight" }
.that(leftAEWindowRegion.region.bounds.height())
.isEqual(rightAEWindowRegion.region.bounds.height())
- check { "SystemSplitHeight" }
- .that(rightAEWindowRegion.region.bounds.height())
- .isEqual(secondaryAppLayerRegion.region.bounds.height())
check { "ActivityEmbeddingSplitWidth" }
.that(
abs(
@@ -187,14 +171,6 @@
)
)
.isLower(2)
- check { "SystemSplitWidth" }
- .that(
- abs(
- secondaryAppLayerRegion.region.bounds.width() -
- 2 * rightAEWindowRegion.region.bounds.width()
- )
- )
- .isLower(2)
}
}
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 57a58c8..4ffb11a 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 2cb86e0..0fa4d07 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 2cf85fa..4d9fefb 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b93e1be..b879c54 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -82,6 +82,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/IME/OWNERS b/tests/FlickerTests/IME/OWNERS
index ae1098d..e3a2e67 100644
--- a/tests/FlickerTests/IME/OWNERS
+++ b/tests/FlickerTests/IME/OWNERS
@@ -1,3 +1,3 @@
# ime
# Bug component: 34867
-include /services/core/java/com/android/server/inputmethod/OWNERS
+file:/services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index da8368f..2b6ddcb 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -32,6 +32,9 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+/**
+ * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest`
+ */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 2f3ec63..0344197 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -33,8 +33,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window closing to home transitions. To run this test: `atest
- * FlickerTests:CloseImeWindowToHomeTest`
+ * Test IME window closing to home transitions.
+ * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index 8821b69..fde1373 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -42,7 +42,7 @@
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index d75eba6..dc50135 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -42,7 +42,7 @@
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 41d9e30..dc2bd1b 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -34,8 +34,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window closing back to app window transitions. To run this test: `atest
- * FlickerTests:CloseImeWindowToAppTest`
+ * Test IME window closing back to app window transitions.
+ * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 0e7fb79..05771e8 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -40,7 +40,7 @@
* Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify
* there is no flickering when back to the simple activity without requesting IME to show.
*
- * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 47a7e1b..336fe6f 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -36,8 +36,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest
- * FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ * Test IME window shown on the app with fixing portrait orientation.
+ * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 48ec4d1..b8f11dc 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -38,8 +38,9 @@
/**
* Test IME window layer will become visible when switching from the fixed orientation activity
- * (e.g. Launcher activity). To run this test: `atest
- * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
+ * (e.g. Launcher activity).
+ * To run this test:
+ * `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 03f3a68..34a7085 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -33,7 +33,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest`
+ * Test IME window opening transitions.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 7b62c89..7c72c31 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -35,8 +35,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest
- * FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 53bfb4e..fe5320c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -36,7 +36,7 @@
/**
* Launch an app that automatically displays the IME
*
- * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest`
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index d22bdcf..92b6b93 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -35,8 +35,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window closing on lock and opening on screen unlock. To run this test: `atest
- * FlickerTests:CloseImeWindowToHomeTest`
+ * Test IME window closing on lock and opening on screen unlock.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 12290af..9eaf998 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -31,7 +31,10 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */
+/**
+ * Test IME window opening transitions.
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest`
+ */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 0948351..7186a2c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -41,7 +41,7 @@
/**
* Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
- * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest`
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 7aa525f..c96c760 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -37,8 +37,8 @@
import org.junit.runners.Parameterized
/**
- * Test IME window layer will be associated with the app task when going to the overview screen. To
- * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest`
+ * Test IME window layer will be associated with the app task when going to the overview screen.
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 9c6a17d3..04b312a 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
index ffaeead..8c9ab9a 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
@@ -40,7 +40,7 @@
*
* This test assumes the device doesn't have AOD enabled
*
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationColdTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
index 6e67e19..e595100a 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -40,7 +40,7 @@
*
* This test assumes the device doesn't have AOD enabled
*
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWarmTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index f1df8a6..fbe1d34 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -40,7 +40,8 @@
*
* This test assumes the device doesn't have AOD enabled
*
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
+ * To run this test:
+ * `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWithOverlayAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
index b6d09d0..c8ca644 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
@@ -36,7 +36,7 @@
/**
* Test cold launching an app from a notification.
*
- * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationColdTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index 1e607bf..c29e71c 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -47,7 +47,7 @@
/**
* Test cold launching an app from a notification.
*
- * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationWarmTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index ecbed28..8acdabc 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 1eacdfd..91ece21 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
value="trace_config.textproto"
/>
<option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9198ae1..3e500d9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -18,7 +18,10 @@
package="com.android.server.wm.flicker.testapp">
<uses-sdk android:minSdkVersion="29"
- android:targetSdkVersion="29"/>
+ android:targetSdkVersion="35"/>
+
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
<application android:allowBackup="false"
android:supportsRtl="true">
<uses-library android:name="androidx.window.extensions" android:required="false"/>
@@ -107,7 +110,7 @@
android:immersive="true"
android:resizeableActivity="true"
android:screenOrientation="portrait"
- android:theme="@android:style/Theme.NoTitleBar"
+ android:theme="@style/OptOutEdgeToEdge.NoTitleBar"
android:configChanges="screenSize"
android:label="PortraitImmersiveActivity"
android:exported="true">
@@ -119,7 +122,7 @@
<activity android:name=".LaunchTransparentActivity"
android:resizeableActivity="false"
android:screenOrientation="portrait"
- android:theme="@android:style/Theme"
+ android:theme="@style/OptOutEdgeToEdge"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
android:label="LaunchTransparentActivity"
android:exported="true">
@@ -273,7 +276,7 @@
android:exported="true"
android:label="MailActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
- android:theme="@style/Theme.AppCompat.Light">
+ android:theme="@style/OptOutEdgeToEdge.AppCompatTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -282,7 +285,7 @@
<activity android:name=".GameActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
android:immersive="true"
- android:theme="@android:style/Theme.NoTitleBar"
+ android:theme="@style/OptOutEdgeToEdge.NoTitleBar"
android:configChanges="screenSize"
android:label="GameActivity"
android:exported="true">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 86c21906..917aec1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -14,66 +14,71 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
android:background="@android:color/holo_orange_light">
- <Button
- android:id="@+id/launch_secondary_activity_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Secondary Activity" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <Button
- android:id="@+id/launch_secondary_activity_rtl_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="RIGHT_TO_LEFT"
- android:text="Launch Secondary Activity in RTL" />
+ <Button
+ android:id="@+id/launch_secondary_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Secondary Activity" />
- <Button
- android:id="@+id/launch_secondary_activity_horizontally_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="BOTTOM_TO_TOP"
- android:text="Launch Secondary Activity Horizontally" />
+ <Button
+ android:id="@+id/launch_secondary_activity_rtl_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="RIGHT_TO_LEFT"
+ android:text="Launch Secondary Activity in RTL" />
- <Button
- android:id="@+id/launch_placeholder_split_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchPlaceholderSplit"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Placeholder Split" />
+ <Button
+ android:id="@+id/launch_secondary_activity_horizontally_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="BOTTOM_TO_TOP"
+ android:text="Launch Secondary Activity Horizontally" />
- <Button
- android:id="@+id/launch_always_expand_activity_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchAlwaysExpandActivity"
- android:text="Launch Always Expand Activity" />
+ <Button
+ android:id="@+id/launch_placeholder_split_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchPlaceholderSplit"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Placeholder Split" />
- <Button
- android:id="@+id/launch_placeholder_split_rtl_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchPlaceholderSplit"
- android:tag="RIGHT_TO_LEFT"
- android:text="Launch Placeholder Split in RTL" />
+ <Button
+ android:id="@+id/launch_always_expand_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchAlwaysExpandActivity"
+ android:text="Launch Always Expand Activity" />
- <Button
- android:id="@+id/launch_trampoline_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchTrampolineActivity"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Trampoline Activity" />
+ <Button
+ android:id="@+id/launch_placeholder_split_rtl_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchPlaceholderSplit"
+ android:tag="RIGHT_TO_LEFT"
+ android:text="Launch Placeholder Split in RTL" />
-</LinearLayout>
+ <Button
+ android:id="@+id/launch_trampoline_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchTrampolineActivity"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Trampoline Activity" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 9b742d9..47d1137 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -16,7 +16,19 @@
-->
<resources>
- <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
+ <style name="OptOutEdgeToEdge" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
+
+ <style name="OptOutEdgeToEdge.NoTitleBar" parent="@android:style/Theme.NoTitleBar">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
+
+ <style name="OptOutEdgeToEdge.AppCompatTheme" parent="@style/Theme.AppCompat.Light">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
+
+ <style name="DefaultTheme" parent="@style/OptOutEdgeToEdge">
<item name="android:windowBackground">@android:color/darker_gray</item>
</style>
@@ -32,7 +44,7 @@
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
</style>
- <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault">
+ <style name="DialogTheme" parent="@style/OptOutEdgeToEdge">
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@null</item>
@@ -43,18 +55,18 @@
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
- <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+ <style name="TransparentTheme" parent="@style/OptOutEdgeToEdge">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
- <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
+ <style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
- <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault">
+ <style name="SplashscreenAppTheme" parent="@style/OptOutEdgeToEdge">
<!-- Splashscreen Attributes -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item>
<!-- Here we want to match the duration of our AVD -->
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
index c92b82b..a86ba5f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
@@ -125,7 +125,7 @@
.setContentTitle("BubbleChat")
.setContentIntent(PendingIntent.getActivity(mContext, 0,
new Intent(mContext, LaunchBubbleActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT))
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE))
.setStyle(new Notification.MessagingStyle(chatBot)
.setConversationTitle("BubbleChat")
.addMessage("BubbleChat",
@@ -140,7 +140,7 @@
Intent target = new Intent(mContext, BubbleActivity.class);
target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
return new Notification.BubbleMetadata.Builder()
.setIntent(bubbleIntent)
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
index dea3444..37332c9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
@@ -17,6 +17,9 @@
package com.android.server.wm.flicker.testapp;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.app.Activity;
import android.app.Person;
import android.content.Context;
@@ -24,6 +27,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
+import android.os.Build;
import android.os.Bundle;
import android.view.View;
@@ -36,6 +40,13 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
+ // POST_NOTIFICATIONS permission required for notification post sdk 33.
+ requestPermissions(new String[] { POST_NOTIFICATIONS }, 0);
+ }
+
addInboxShortcut(getApplicationContext());
mBubbleHelper = BubbleHelper.getInstance(this);
setContentView(R.layout.activity_main);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
index a4dd575..d6427ab 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
@@ -16,6 +16,9 @@
package com.android.server.wm.flicker.testapp;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -23,6 +26,7 @@
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.Button;
@@ -34,6 +38,13 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
+ // POST_NOTIFICATIONS permission required for notification post sdk 33.
+ requestPermissions(new String[] { POST_NOTIFICATIONS }, 0);
+ }
+
WindowManager.LayoutParams p = getWindow().getAttributes();
p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 1ab8ddb..27eb5a0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -198,7 +198,7 @@
filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
filter.addAction(ACTION_ENTER_PIP);
filter.addAction(ACTION_ASPECT_RATIO);
- registerReceiver(mBroadcastReceiver, filter);
+ registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
handleIntentExtra(getIntent());
}
@@ -222,8 +222,8 @@
private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
final Intent intent = new Intent(action);
- final PendingIntent pendingIntent =
- PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
return new RemoteAction(icon, label, label, pendingIntent);
}
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index 3a2a3be..ae32bda 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,6 +16,8 @@
package android.hardware.input
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import android.platform.test.annotations.Presubmit
@@ -54,16 +56,16 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
- setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
val drawable = createDrawable()!!
assertEquals(WIDTH, drawable.intrinsicWidth)
assertEquals(HEIGHT, drawable.intrinsicHeight)
}
@Test
+ @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
- setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
assertNull(createDrawable())
}
}
\ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index e2b0c36..bcd56ad 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,6 +21,7 @@
import android.os.Handler
import android.os.HandlerExecutor
import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
@@ -50,6 +51,10 @@
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
+@EnableFlags(
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL,
+)
class StickyModifierStateListenerTest {
@get:Rule
@@ -67,10 +72,6 @@
@Before
fun setUp() {
- // Enable Sticky keys feature
- rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
- rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL)
-
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
inputManager = InputManager(context)
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 001a09a..be9fb1b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -34,7 +34,7 @@
import perfetto.protos.ProtologCommon;
import perfetto.protos.ProtologConfig;
-public class PerfettoDataSourceTest {
+public class ProtologDataSourceTest {
@Before
public void before() {
assumeTrue(android.tracing.Flags.perfettoProtologTracing());
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
new file mode 100644
index 0000000..d6f3148
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.hardware.usb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.flags.Flags;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+
+/**
+ * Unit tests for {@link android.hardware.usb.DeviceFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceFilterTest {
+
+ private static final int VID = 10;
+ private static final int PID = 11;
+ private static final int CLASS = 12;
+ private static final int SUBCLASS = 13;
+ private static final int PROTOCOL = 14;
+ private static final String MANUFACTURER = "Google";
+ private static final String PRODUCT = "Test";
+ private static final String SERIAL_NO = "4AL23";
+ private static final String INTERFACE_NAME = "MTP";
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(Flags.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructorFromValues_interfaceNameIsInitialized() {
+ DeviceFilter deviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+ }
+
+ @Test
+ public void testConstructorFromUsbDevice_interfaceNameIsNull() {
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getVendorId()).thenReturn(VID);
+ when(usbDevice.getProductId()).thenReturn(PID);
+ when(usbDevice.getDeviceClass()).thenReturn(CLASS);
+ when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS);
+ when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL);
+ when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER);
+ when(usbDevice.getProductName()).thenReturn(PRODUCT);
+ when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO);
+
+ DeviceFilter deviceFilter = new DeviceFilter(usbDevice);
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+ }
+
+ @Test
+ public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() {
+ DeviceFilter originalDeviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter);
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+ }
+
+
+ @Test
+ public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+
+ assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP");
+ }
+
+ @Test
+ public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+ }
+
+ @Test
+ public void testWrite_withInterfaceName() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+ XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+ deviceFilter.write(serializer);
+
+ verify(serializer).attribute(null, "interface-name", "MTP");
+ }
+
+ @Test
+ public void testWrite_withoutInterfaceName() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+ XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+ deviceFilter.write(serializer);
+
+ verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any());
+ }
+
+ @Test
+ public void testToString() {
+ DeviceFilter deviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ assertThat(deviceFilter.toString()).isEqualTo(
+ "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14,"
+ + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23,"
+ + "mInterfaceName=MTP]");
+ }
+
+ @Test
+ public void testMatch_interfaceNameMatches_returnTrue() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "MTP",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertTrue(deviceFilter.matches(usbDevice));
+ }
+
+ @Test
+ public void testMatch_interfaceNameMismatch_returnFalse() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "UVC",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertFalse(deviceFilter.matches(usbDevice));
+ }
+
+ @Test
+ public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception {
+ when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false);
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "UVC",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertTrue(deviceFilter.matches(usbDevice));
+ }
+
+ private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) {
+ assertThat(deviceFilter.mVendorId).isEqualTo(VID);
+ assertThat(deviceFilter.mProductId).isEqualTo(PID);
+ assertThat(deviceFilter.mClass).isEqualTo(CLASS);
+ assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS);
+ assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL);
+ assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER);
+ assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT);
+ assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO);
+ }
+
+ private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ XmlPullParser parser = factory.newPullParser();
+ parser.setInput(new StringReader(xml));
+ XmlUtils.nextElement(parser);
+
+ return DeviceFilter.read(parser);
+ }
+
+}
diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h
index de1d60c..24c3208 100644
--- a/tools/aapt/Symbol.h
+++ b/tools/aapt/Symbol.h
@@ -40,7 +40,7 @@
};
/**
- * A specific defintion of a symbol, defined with a configuration and a definition site.
+ * A specific definition of a symbol, defined with a configuration and a definition site.
*/
struct SymbolDefinition {
inline SymbolDefinition();
@@ -92,4 +92,3 @@
}
#endif // AAPT_SYMBOL_H
-
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 16785d1..6b360b7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -25,6 +25,7 @@
val aidlPolicy: FilterPolicyWithReason?,
val featureFlagsPolicy: FilterPolicyWithReason?,
val syspropsPolicy: FilterPolicyWithReason?,
+ val rFilePolicy: FilterPolicyWithReason?,
fallback: OutputFilter
) : DelegatingFilter(fallback) {
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -37,6 +38,9 @@
if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
return syspropsPolicy
}
+ if (rFilePolicy != null && classes.isRClass(className)) {
+ return rFilePolicy
+ }
return super.getPolicyForClass(className)
}
}
@@ -74,3 +78,10 @@
return className.startsWith("android/sysprop/")
&& className.endsWith("Properties")
}
+
+/**
+ * @return if a given class "seems like" an R class or its nested classes.
+ */
+private fun ClassNodes.isRClass(className: String): Boolean {
+ return className.endsWith("/R") || className.contains("/R$")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 75b5fc8..c5acd81 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -17,6 +17,7 @@
import com.android.hoststubgen.ParseException
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.whitespaceRegex
@@ -31,13 +32,17 @@
* Print a class node as a "keep" policy.
*/
fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {
- pw.printf("class %s\t%s\n", cn.name, "keep")
+ pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep")
- for (f in cn.fields ?: emptyList()) {
- pw.printf(" field %s\t%s\n", f.name, "keep")
+ cn.fields?.let {
+ for (f in it.sortedWith(compareBy({ it.name }))) {
+ pw.printf(" field %s %s\n", f.name, "keep")
+ }
}
- for (m in cn.methods ?: emptyList()) {
- pw.printf(" method %s\t%s\t%s\n", m.name, m.desc, "keep")
+ cn.methods?.let {
+ for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) {
+ pw.printf(" method %s %s %s\n", m.name, m.desc, "keep")
+ }
}
}
@@ -66,6 +71,7 @@
var aidlPolicy: FilterPolicyWithReason? = null
var featureFlagsPolicy: FilterPolicyWithReason? = null
var syspropsPolicy: FilterPolicyWithReason? = null
+ var rFilePolicy: FilterPolicyWithReason? = null
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -162,6 +168,14 @@
syspropsPolicy = policy.withReason(
"$FILTER_REASON (special-class sysprops)")
}
+ SpecialClass.RFile -> {
+ if (rFilePolicy != null) {
+ throw ParseException(
+ "Policy for R file already defined")
+ }
+ rFilePolicy = policy.withReason(
+ "$FILTER_REASON (special-class R file)")
+ }
}
}
}
@@ -225,13 +239,9 @@
throw e.withSourceInfo(filename, lineNo)
}
- var ret: OutputFilter = imf
- if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
- log.d("AndroidHeuristicsFilter enabled")
- ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
- }
- return ret
+ // Wrap the in-memory-filter with AHF.
+ return AndroidHeuristicsFilter(
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf)
}
}
@@ -240,6 +250,7 @@
Aidl,
FeatureFlags,
Sysprops,
+ RFile,
}
private fun resolveSpecialClass(className: String): SpecialClass {
@@ -250,6 +261,7 @@
":aidl" -> return SpecialClass.Aidl
":feature_flags" -> return SpecialClass.FeatureFlags
":sysprops" -> return SpecialClass.Sysprops
+ ":r" -> return SpecialClass.RFile
}
throw ParseException("Invalid special class name \"$className\"")
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index fa8fe6c..931f0c5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -322,6 +322,78 @@
InnerClasses:
public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 3
+ public static int[] ARRAY;
+ descriptor: [I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+ public com.android.hoststubgen.test.tinyframework.R$Nested();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=4, locals=0, args_size=0
+ x: iconst_1
+ x: newarray int
+ x: dup
+ x: iconst_0
+ x: iconst_1
+ x: iastore
+ x: putstatic #x // Field ARRAY:[I
+ x: return
+ LineNumberTable:
+}
+SourceFile: "R.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 3
+ public com.android.hoststubgen.test.tinyframework.R();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R;
+}
+SourceFile: "R.java"
+NestMembers:
+ com/android/hoststubgen/test/tinyframework/R$Nested
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index c605f76..906a81c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -122,6 +122,100 @@
NestMembers:
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+ public static int[] ARRAY;
+ descriptor: [I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.R$Nested();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=3, locals=0, args_size=0
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 4
+ public com.android.hoststubgen.test.tinyframework.R();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+ com/android/hoststubgen/test/tinyframework/R$Nested
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 11d5939..10bc91d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -348,6 +348,108 @@
NestMembers:
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+ public static int[] ARRAY;
+ descriptor: [I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.R$Nested();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=4, locals=0, args_size=0
+ x: iconst_1
+ x: newarray int
+ x: dup
+ x: iconst_0
+ x: iconst_1
+ x: iastore
+ x: putstatic #x // Field ARRAY:[I
+ x: return
+ LineNumberTable:
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 4
+ public com.android.hoststubgen.test.tinyframework.R();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+ com/android/hoststubgen/test/tinyframework/R$Nested
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index c605f76..906a81c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -122,6 +122,100 @@
NestMembers:
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+ public static int[] ARRAY;
+ descriptor: [I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.R$Nested();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=3, locals=0, args_size=0
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 4
+ public com.android.hoststubgen.test.tinyframework.R();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+ com/android/hoststubgen/test/tinyframework/R$Nested
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 088bc80..fcf9a8c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -481,6 +481,136 @@
NestMembers:
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+ public static int[] ARRAY;
+ descriptor: [I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public com.android.hoststubgen.test.tinyframework.R$Nested();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=4, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested
+ x: ldc #x // String <clinit>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: iconst_1
+ x: newarray int
+ x: dup
+ x: iconst_0
+ x: iconst_1
+ x: iastore
+ x: putstatic #x // Field ARRAY:[I
+ x: return
+ LineNumberTable:
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+ Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/R
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 2, attributes: 4
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/R
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.R();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/R
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+ public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+ com/android/hoststubgen/test/tinyframework/R$Nested
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
Compiled from "TinyFrameworkCallerCheck.java"
class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index d302084..696b6d0 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -19,6 +19,9 @@
# Heuristics rule: Stub all the AIDL classes.
class :aidl stubclass
+# Heuristics rule: Stub all the R classes.
+class :r stubclass
+
# Default is "remove", so let's put all the base classes / interfaces in the stub first.
class com.android.hoststubgen.test.tinyframework.subclasstest.C1 stub
class com.android.hoststubgen.test.tinyframework.subclasstest.C2 stub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java
new file mode 100644
index 0000000..b1bedf4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+public class R {
+ public static class Nested {
+ public static int[] ARRAY = new int[] {1};
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 762180d..37925e8 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.fail;
+import com.android.hoststubgen.test.tinyframework.R.Nested;
import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
import org.junit.Rule;
@@ -328,4 +329,9 @@
assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2);
assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3);
}
+
+ @Test
+ public void testRFileHeuristics() {
+ assertThat(Nested.ARRAY.length).isEqualTo(1);
+ }
}