Merge "Use "SessionHint" enum in ndk API"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b71d596..5998ab7 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -271,34 +271,110 @@
/**
* Tests starting an uninitialized user, with wait times in between iterations.
- * Measures the time until the ProgressListener callback.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for
+ * a new user.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startUser_realistic() throws RemoteException {
+ public void startUser_uninitializedUser() throws RemoteException {
+ startUser_measuresAfterFirstIterations(/* numberOfIterationsToSkip */0);
+ }
+
+ /**
+ * Tests the second iteration of start user that has a problem that it takes too long to run, a
+ * bug has been created (b/266574680) and after investigating or fix this problem,
+ * this test can be removed.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startUser_startOnceBefore() throws RemoteException {
+ startUser_measuresAfterFirstIterations(/* numberOfIterationsToSkip */1);
+ }
+
+ /**
+ * Tests a specific iteration of the start user process.
+ * Measures the time until ACTION_USER_STARTED is received.
+ * @param numberOfIterationsToSkip number of iterations that must be skipped in the preStartUser
+ * method.
+ */
+ private void startUser_measuresAfterFirstIterations(int numberOfIterationsToSkip)
+ throws RemoteException {
+ /**
+ * Run start user and stop for the next iteration, measures time while mRunner.keepRunning()
+ * return true.
+ */
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+
final int userId = createUserNoFlags();
- final ProgressWaiter waiter = new ProgressWaiter();
+
+ preStartUser(userId, numberOfIterationsToSkip);
waitForBroadcastIdle();
- mRunner.resumeTiming();
- Log.i(TAG, "Starting timer");
+ waitCoolDownPeriod();
- final boolean success = mIam.startUserInBackgroundWithListener(userId, waiter)
- && waiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
+ runThenWaitForBroadcasts(userId, () -> {
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- assertTrue("Error: could not start user " + userId, success);
-
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
/**
+ * Tests starting an initialized user, with wait times in between iterations stopping between
+ * iterations,this test will skip the first two iterations and only measure the next ones.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for
+ * a new user.
+ *
+ * The second iteration takes longer than expected and has a bug (b/266574680) to investigate
+ * it.
+ *
+ * The next iterations take the expected time to start a user.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startUser_startTwiceBefore() throws RemoteException {
+ final int userId = createUserNoFlags();
+
+ //TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
+ preStartUser(userId, /* numberOfIterations */2);
+
+ /**
+ * Run start user and stop for the next iteration, measures time while mRunner.keepRunning()
+ * return true.
+ */
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+
+ stopUser(userId, /* force */true);
+ mRunner.resumeTimingForNextIteration();
+ }
+
+ removeUser(userId);
+ }
+
+
+ /**
* Tests starting & unlocking an uninitialized user.
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@@ -321,6 +397,46 @@
}
/**
+ * Tests starting & unlocking an initialized user, stopping the user at the end simulating real
+ * usage where the user is not removed after created and initialized.
+ * Measures the time until unlock listener is triggered and user is unlocked.
+ * This test will skip the first two iterations and only measure the next ones.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for a
+ * new user.
+ *
+ * The second iteration takes longer than expected and has a bug (b/266574680) to investigate
+ * it.
+ *
+ * The next iterations take the expected time to start a user.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startAndUnlockUser_startTwiceBefore() throws RemoteException {
+ final int userId = createUserNoFlags();
+
+ //TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
+ preStartUser(userId, /* numberOfIterations */2);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ // Waits for UserState.mUnlockProgress.finish().
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ stopUser(userId, /* force */true);
+ mRunner.resumeTimingForNextIteration();
+ }
+
+ removeUser(userId);
+ }
+
+ /**
* Tests starting & unlocking an uninitialized user.
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@@ -404,28 +520,36 @@
}
}
- /** Tests switching to a previously-started, but no-longer-running, user with wait
- * times between iterations */
+ /**
+ * Tests switching to a previously-started, but no-longer-running, user with wait
+ * times between iterations
+ **/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_stopped_realistic() throws RemoteException {
- final int startUser = ActivityManager.getCurrentUser();
- final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+ final int currentUserId = ActivityManager.getCurrentUser();
+ final int userId = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+
+ /**
+ * Skip the second iteration of start user process that is taking a long time to finish.
+ */
+ preStartUser(userId, /* numberOfIterations */1);
+
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
- switchUser(testUser);
+ switchUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
- switchUserNoCheck(startUser);
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
- attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+ switchUserNoCheck(currentUserId);
+ stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
mRunner.resumeTimingForNextIteration();
}
- removeUser(testUser);
+ removeUser(userId);
}
/** Tests switching to a previously-started, but no-longer-running, user with wait
@@ -1378,6 +1502,24 @@
}
}
+ /**
+ * Start the user and stop after that, will repeat numberOfIterations times.
+ * Make sure the user is started before proceeding with the test.
+ * @param userId identifier of the user that will be started.
+ * @param numberOfIterations number of iterations that must be skipped.
+ */
+ private void preStartUser(int userId, int numberOfIterations) throws RemoteException {
+ for (int i = 0; i < numberOfIterations; i++) {
+ final ProgressWaiter preWaiter = new ProgressWaiter();
+
+ final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
+ preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
+ stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+
+ assertTrue("Pre start was not performed for user" + userId, preStartComplete);
+ }
+ }
+
private void fail(@NonNull String message) {
Log.e(TAG, "Test failed on iteration #" + mRunner.getIteration() + ": " + message);
mRunner.markAsFailed(new AssertionError(message));
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 911744f..514d984 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -648,7 +648,7 @@
"last work dequeued");
// This will finish the job.
doCallbackLocked(false, "last work dequeued");
- } else {
+ } else if (work != null) {
// Delivery count has been updated, so persist JobWorkItem change.
mService.mJobs.touchJob(mRunningJob);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 8d27311..dfc2602 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5206,6 +5206,13 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.BackgroundServiceStartNotAllowedException> CREATOR;
}
+ public class BroadcastOptions {
+ method public boolean isShareIdentityEnabled();
+ method @NonNull public static android.app.BroadcastOptions makeBasic();
+ method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
+ method @NonNull public android.os.Bundle toBundle();
+ }
+
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
ctor public DatePickerDialog(@NonNull android.content.Context);
ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
@@ -9569,6 +9576,8 @@
method public final int getResultCode();
method public final String getResultData();
method public final android.os.Bundle getResultExtras(boolean);
+ method @Nullable public String getSentFromPackage();
+ method public int getSentFromUid();
method public final android.content.BroadcastReceiver.PendingResult goAsync();
method public final boolean isInitialStickyBroadcast();
method public final boolean isOrderedBroadcast();
@@ -10169,11 +10178,14 @@
method public abstract void revokeUriPermission(String, android.net.Uri, int);
method public abstract void sendBroadcast(@RequiresPermission android.content.Intent);
method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
+ method public void sendBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String);
method public void sendBroadcastWithMultiplePermissions(@NonNull android.content.Intent, @NonNull String[]);
method public abstract void sendOrderedBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
+ method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
+ method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyBroadcast(@RequiresPermission android.content.Intent);
@@ -12096,11 +12108,11 @@
public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
method public int describeContents();
- method public boolean isRequireAppNotForeground();
- method public boolean isRequireAppNotInteracting();
- method public boolean isRequireAppNotTopVisible();
- method public boolean isRequireDeviceIdle();
- method public boolean isRequireNotInCall();
+ method public boolean isAppNotForegroundRequired();
+ method public boolean isAppNotInteractingRequired();
+ method public boolean isAppNotTopVisibleRequired();
+ method public boolean isDeviceIdleRequired();
+ method public boolean isNotInCallRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
@@ -12109,16 +12121,16 @@
public static final class PackageInstaller.InstallConstraints.Builder {
ctor public PackageInstaller.InstallConstraints.Builder();
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired();
}
public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+ method public boolean areAllConstraintsSatisfied();
method public int describeContents();
- method public boolean isAllConstraintsSatisfied();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
}
@@ -12185,12 +12197,12 @@
method @Nullable public String getAppPackageName();
method @NonNull public int[] getChildSessionIds();
method public long getCreatedMillis();
+ method public boolean getDontKillApp();
method public int getInstallLocation();
method public int getInstallReason();
method @Nullable public String getInstallerAttributionTag();
method @Nullable public String getInstallerPackageName();
method public int getInstallerUid();
- method @NonNull public boolean getIsPreApprovalRequested();
method public int getMode();
method public int getOriginatingUid();
method @Nullable public android.net.Uri getOriginatingUri();
@@ -12210,6 +12222,7 @@
method public boolean isApplicationEnabledSettingPersistent();
method public boolean isCommitted();
method public boolean isMultiPackage();
+ method public boolean isPreApprovalRequested();
method public boolean isRequestUpdateOwnership();
method public boolean isSealed();
method public boolean isStaged();
@@ -12240,6 +12253,7 @@
method public void setAppPackageName(@Nullable String);
method public void setApplicationEnabledSettingPersistent();
method @Deprecated public void setAutoRevokePermissionsMode(boolean);
+ method public void setDontKillApp(boolean);
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setInstallScenario(int);
@@ -27421,6 +27435,8 @@
method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
method public void onTracksChanged(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public void onTuned(@NonNull android.net.Uri);
+ method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
+ method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void onVideoAvailable();
method public void onVideoUnavailable(int);
method @CallSuper public void removeBroadcastInfo(int);
@@ -27483,6 +27499,8 @@
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendStreamVolume(float);
method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
+ method public void sendTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
+ method public void sendTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener);
method public void setTeletextAppEnabled(boolean);
@@ -32122,9 +32140,11 @@
method public void onEarlyReportFinished();
method public void onError(int);
method public void onFinished();
+ method public void onFinished(@NonNull String);
method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float);
field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5
field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
+ field public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6; // 0x6
field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
@@ -41631,7 +41651,6 @@
public final class CallControl {
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
- method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -41680,7 +41699,6 @@
}
public final class CallException extends java.lang.RuntimeException implements android.os.Parcelable {
- ctor public CallException(@Nullable String);
ctor public CallException(@Nullable String, int);
method public int describeContents();
method public int getCode();
@@ -52455,7 +52473,9 @@
method public int getScaledHoverSlop();
method public int getScaledMaximumDrawingCacheSize();
method public int getScaledMaximumFlingVelocity();
+ method public int getScaledMaximumFlingVelocity(int, int, int);
method public int getScaledMinimumFlingVelocity();
+ method public int getScaledMinimumFlingVelocity(int, int, int);
method public int getScaledMinimumScalingSpan();
method public int getScaledOverflingDistance();
method public int getScaledOverscrollDistance();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fc7b9d4..fe3605f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -817,7 +817,6 @@
method public int getPendingIntentBackgroundActivityStartMode();
method public boolean isDeferUntilActive();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
- method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
@@ -832,7 +831,6 @@
method public void setRequireNoneOfPermissions(@Nullable String[]);
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
- method public android.os.Bundle toBundle();
field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
@@ -3232,10 +3230,8 @@
method public abstract boolean isCredentialProtectedStorage();
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler);
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
- method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
- method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -3298,9 +3294,7 @@
method public android.content.Context createCredentialProtectedStorageContext();
method @Nullable public java.io.File getPreloadsFileCache();
method public boolean isCredentialProtectedStorage();
- method public void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
@@ -3592,7 +3586,7 @@
}
public class PackageInstaller {
- method @NonNull public android.content.pm.PackageInstaller.InstallInfo getInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3632,7 +3626,6 @@
method public boolean getAllocateAggressive();
method @Deprecated public boolean getAllowDowngrade();
method public int getAutoRevokePermissionsMode();
- method public boolean getDontKillApp();
method public boolean getEnableRollback();
method @Nullable public String[] getGrantedRuntimePermissions();
method public boolean getInstallAsFullApp(boolean);
@@ -3648,7 +3641,6 @@
method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
method @Deprecated public void setAllowDowngrade(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
- method public void setDontKillApp(boolean);
method public void setEnableRollback(boolean);
method public void setEnableRollback(boolean, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
@@ -7961,6 +7953,7 @@
public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getAudioHandle();
+ method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
method public long getDataLength();
method public long getDts();
@@ -9939,6 +9932,7 @@
public final class BugreportManager {
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData();
method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
+ method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void retrieveBugreport(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
@@ -9947,6 +9941,7 @@
ctor public BugreportParams(int, int);
method public int getFlags();
method public int getMode();
+ field public static final int BUGREPORT_FLAG_DEFER_CONSENT = 2; // 0x2
field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7a460ee..62a1eb9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -165,7 +165,7 @@
method @Nullable public String getIconResourcePackage();
}
- public class ActivityOptions {
+ public class ActivityOptions extends android.app.ComponentOptions {
method public boolean isEligibleForLegacyPermissionPrompt();
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
@@ -269,7 +269,7 @@
method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
}
- public class BroadcastOptions {
+ public class BroadcastOptions extends android.app.ComponentOptions {
ctor public BroadcastOptions(@NonNull android.os.Bundle);
method @Deprecated public int getMaxManifestReceiverApiLevel();
method public long getTemporaryAppAllowlistDuration();
@@ -282,6 +282,12 @@
field public static final long CHANGE_ALWAYS_ENABLED = 209888056L; // 0xc82a338L
}
+ public class ComponentOptions {
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
+ }
+
public class DownloadManager {
field public static final String COLUMN_MEDIASTORE_URI = "mediastore_uri";
}
@@ -816,6 +822,7 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -3148,7 +3155,9 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2a390a7..b57fb20 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -88,6 +88,16 @@
*/
public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+ /** No explicit value chosen. The system will decide whether to grant privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+
/**
* The package name that created the options.
* @hide
@@ -2430,6 +2440,30 @@
return super.getPendingIntentBackgroundActivityStartMode();
}
+ /**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ *
+ * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+ * of states
+ */
+ @Override
+ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller can start
+ * background activities.
+ *
+ * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+ * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+ * not match the actual behavior if the value was not explicitly set.
+ */
+ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
/** @hide */
@Override
public String toString() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3761251..170c0b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -785,9 +785,10 @@
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
- assumeDelivered, token, sendingUser, intent.getFlags());
+ assumeDelivered, token, sendingUser, intent.getFlags(), sentFromUid,
+ sentFromPackage);
this.intent = intent;
}
@@ -801,7 +802,8 @@
return "ReceiverData{intent=" + intent + " packageName=" +
info.packageName + " resultCode=" + getResultCode()
+ " resultData=" + getResultData() + " resultExtras="
- + getResultExtras(false) + "}";
+ + getResultExtras(false) + " sentFromUid="
+ + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + "}";
}
}
@@ -1041,10 +1043,12 @@
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
- boolean ordered, boolean assumeDelivered, int sendingUser, int processState) {
+ boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, String sentFromPackage) {
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
- ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser);
+ ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
+ sentFromUid, sentFromPackage);
r.info = info;
sendMessage(H.RECEIVER, r);
}
@@ -1055,11 +1059,13 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky,
- r.assumeDelivered, r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sentFromUid, r.sentFromPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync,
- r.assumeDelivered, r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sentFromUid, r.sentFromPackage);
}
}
}
@@ -1289,7 +1295,8 @@
// applies transaction ordering per object for such calls.
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState)
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, String sentFromPackage)
throws RemoteException {
updateProcessState(processState, false);
@@ -1299,12 +1306,19 @@
// report an expected delivery event
if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
- resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser);
+ resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
+ sentFromUid, sentFromPackage);
} else {
if (!assumeDelivered) {
Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+ " and " + intent + " without mechanism to finish delivery");
}
+ if (sentFromUid != Process.INVALID_UID || sentFromPackage != null) {
+ Log.wtf(TAG,
+ "scheduleRegisteredReceiver() called for " + receiver + " and " + intent
+ + " from " + sentFromPackage + " (UID: " + sentFromUid
+ + ") without mechanism to propagate the sender's identity");
+ }
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
sendingUser);
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 88765c3..f6992c9 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,6 +27,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
+import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
@@ -46,9 +47,7 @@
* Helper class for building an options Bundle that can be used with
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
- * {@hide}
*/
-@SystemApi
public class BroadcastOptions extends ComponentOptions {
private long mTemporaryAppAllowlistDuration;
private @TempAllowListType int mTemporaryAppAllowlistType;
@@ -64,6 +63,7 @@
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private boolean mIsDeferUntilActive = false;
+ private boolean mShareIdentity = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -172,6 +172,12 @@
"android:broadcast.is_alarm";
/**
+ * Whether the broadcasting app's identity should be available to the receiver.
+ * @see #setShareIdentityEnabled(boolean)
+ */
+ private static final String KEY_SHARE_IDENTITY = "android:broadcast.share_identity";
+
+ /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -270,7 +276,12 @@
*/
public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
- public static BroadcastOptions makeBasic() {
+ /**
+ * Creates a basic {@link BroadcastOptions} with no options initially set.
+ *
+ * @return an instance of {@code BroadcastOptions} against which options can be set
+ */
+ public static @NonNull BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
}
@@ -318,6 +329,7 @@
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+ mShareIdentity = opts.getBoolean(KEY_SHARE_IDENTITY, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -335,8 +347,10 @@
* power allowlist when this broadcast is being delivered to it.
* @param duration The duration in milliseconds; 0 means to not place on allowlist.
* @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead.
+ * @hide
*/
@Deprecated
+ @SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
@@ -350,6 +364,8 @@
* Set a duration for which the system should temporary place an application on the
* power allowlist when this broadcast is being delivered to it, specify the temp allowlist
* type.
+ * @hide
+ *
* @param duration the duration in milliseconds.
* 0 means to not place on allowlist, and clears previous call to this method.
* @param type one of {@link TempAllowListType}.
@@ -360,6 +376,7 @@
* @param reason A human-readable reason explaining why the app is temp allowlisted. Only
* used for logging purposes. Could be null or empty string.
*/
+ @SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
@@ -500,7 +517,9 @@
* Sets whether pending intent can be sent for an application with background restrictions
* @param dontSendToRestrictedApps if true, pending intent will not be sent for an application
* with background restrictions. Default value is {@code false}
+ * @hide
*/
+ @SystemApi
public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) {
mDontSendToRestrictedApps = dontSendToRestrictedApps;
}
@@ -516,7 +535,9 @@
/**
* Sets the process will be able to start activities from background for the duration of
* the broadcast dispatch. Default value is {@code false}
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND)
public void setBackgroundActivityStartsAllowed(boolean allowBackgroundActivityStarts) {
mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
@@ -578,6 +599,7 @@
* <p>
* This requirement applies to both manifest registered and runtime
* registered receivers.
+ * @hide
*
* @param changeId the {@link ChangeId} to inspect
* @param enabled the required enabled state of the inspected
@@ -585,6 +607,7 @@
* @see CompatChanges#isChangeEnabled
* @see #clearRequireCompatChange()
*/
+ @SystemApi
public void setRequireCompatChange(long changeId, boolean enabled) {
mRequireCompatChangeId = changeId;
mRequireCompatChangeEnabled = enabled;
@@ -593,7 +616,9 @@
/**
* Clear any previously defined requirement for this broadcast requested via
* {@link #setRequireCompatChange(long, boolean)}.
+ * @hide
*/
+ @SystemApi
public void clearRequireCompatChange() {
mRequireCompatChangeId = CHANGE_INVALID;
mRequireCompatChangeEnabled = true;
@@ -621,6 +646,39 @@
}
/**
+ * Sets whether the identity of the broadcasting app should be shared with all receivers
+ * that will receive this broadcast.
+ *
+ * <p>Use this option when broadcasting to a receiver that needs to know the identity of the
+ * broadcaster; with this set to {@code true}, the receiver will have access to the broadcasting
+ * app's package name and uid.
+ *
+ * <p>Defaults to {@code false} if not set.
+ *
+ * @param shareIdentityEnabled whether the broadcasting app's identity should be shared with the
+ * receiver
+ * @return {@code this} {@link BroadcastOptions} instance
+ * @see BroadcastReceiver#getSentFromUid()
+ * @see BroadcastReceiver#getSentFromPackage()
+ */
+ public @NonNull BroadcastOptions setShareIdentityEnabled(boolean shareIdentityEnabled) {
+ mShareIdentity = shareIdentityEnabled;
+ return this;
+ }
+
+ /**
+ * Returns whether the broadcasting app has opted-in to sharing its identity with the receiver.
+ *
+ * @return {@code true} if the broadcasting app has opted in to sharing its identity
+ * @see #setShareIdentityEnabled(boolean)
+ * @see BroadcastReceiver#getSentFromUid()
+ * @see BroadcastReceiver#getSentFromPackage()
+ */
+ public boolean isShareIdentityEnabled() {
+ return mShareIdentity;
+ }
+
+ /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -989,7 +1047,7 @@
* extras merger is supplied.
*/
@Override
- public Bundle toBundle() {
+ public @NonNull Bundle toBundle() {
Bundle b = super.toBundle();
if (isTemporaryAppAllowlistSet()) {
b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
@@ -1000,6 +1058,9 @@
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
+ if (mShareIdentity) {
+ b.putBoolean(KEY_SHARE_IDENTITY, true);
+ }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 3776369..9c262b9 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.os.Bundle;
import java.lang.annotation.Retention;
@@ -29,6 +31,10 @@
* Base class for {@link ActivityOptions} and {@link BroadcastOptions}.
* @hide
*/
+// Expose the methods and constants required to test the SystemApis in subclasses.
+@TestApi
+// Suppressed since lint is recommending class have a suffix of Params.
+@SuppressLint("UserHandleName")
public class ComponentOptions {
/**
@@ -57,17 +63,30 @@
private boolean mPendingIntentBalAllowedByPermission = false;
private boolean mIsInteractive = false;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
MODE_BACKGROUND_ACTIVITY_START_DENIED})
public @interface BackgroundActivityStartMode {}
- /** No explicit value chosen. The system will decide whether to grant privileges. */
+ /**
+ * No explicit value chosen. The system will decide whether to grant privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
- /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+ /**
+ * Allow the {@link PendingIntent} to use the background activity start privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
- /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+ /**
+ * Deny the {@link PendingIntent} to use the background activity start privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
ComponentOptions() {
@@ -118,6 +137,7 @@
*
* @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
* of states
+ * @hide
*/
@Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
mPendingIntentBalAllowed = allowed;
@@ -130,6 +150,7 @@
* @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
* targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
* not match the actual behavior if the value was not explicitly set.
+ * @hide
*/
@Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
if (mPendingIntentBalAllowed == null) {
@@ -147,6 +168,7 @@
* methods. A privileged sender of a PendingIntent should only grant
* {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
* and/or executed on behalf the user.
+ * @hide
*/
public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode(
@BackgroundActivityStartMode int state) {
@@ -169,6 +191,7 @@
/**
* Gets the mode for allowing or denying the senders privileges to start background activities
* to the PendingIntent.
+ * @hide
*
* @see #setPendingIntentBackgroundActivityStartMode(int)
*/
@@ -199,6 +222,7 @@
return mPendingIntentBalAllowedByPermission;
}
+ /** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
if (mPendingIntentBalAllowed != null) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 12899f2..bed75db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UiContext;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1358,7 +1359,13 @@
}
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+ sendOrderedBroadcast(intent, receiverPermission, /*options=*/ null);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, Bundle options) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
String[] receiverPermissions = receiverPermission == null ? null
@@ -1368,8 +1375,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, false,
- getUserId());
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, options, true,
+ false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f69d85..dad9b43 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -66,7 +66,8 @@
void scheduleReceiver(in Intent intent, in ActivityInfo info,
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean ordered,
- boolean assumeDelivered, int sendingUser, int processState);
+ boolean assumeDelivered, int sendingUser, int processState, int sentFromUid,
+ in String sentFromPackage);
void scheduleReceiverList(in List<ReceiverInfo> info);
@@ -102,7 +103,8 @@
in String[] args);
void scheduleRegisteredReceiver(IIntentReceiver receiver, in Intent intent,
int resultCode, in String data, in Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState);
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, in String sentFromPackage);
void scheduleLowMemory();
void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
void setSchedulingGroup(int group);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 7c22902..c13da0b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1683,12 +1683,13 @@
performReceive(intent, resultCode, data, extras, ordered, sticky,
BroadcastReceiver.PendingResult.guessAssumeDelivered(
BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
- sendingUser);
+ sendingUser, /*sentFromUid=*/ Process.INVALID_UID,
+ /*sentFromPackage=*/ null);
}
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
final LoadedApk.ReceiverDispatcher rd;
if (intent == null) {
Log.wtf(TAG, "Null intent received");
@@ -1703,7 +1704,8 @@
}
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky, assumeDelivered, sendingUser);
+ ordered, sticky, assumeDelivered, sendingUser,
+ sentFromUid, sentFromPackage);
} else if (!assumeDelivered) {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
@@ -1743,11 +1745,12 @@
private boolean mRunCalled;
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser) {
+ boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser,
+ int sentFromUid, String sentFromPackage) {
super(resultCode, resultData, resultExtras,
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
- intent.getFlags());
+ intent.getFlags(), sentFromUid, sentFromPackage);
mCurIntent = intent;
}
@@ -1871,9 +1874,9 @@
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
- sticky, assumeDelivered, sendingUser);
+ sticky, assumeDelivered, sendingUser, sentFromUid, sentFromPackage);
if (intent == null) {
Log.wtf(TAG, "Null intent received");
} else {
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index 8d7e3c4..7364d0f 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -38,6 +38,8 @@
int sendingUser;
int processState;
int resultCode;
+ int sentFromUid = -1;
+ String sentFromPackage;
/**
* True if this instance represents a registered receiver and false if this instance
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 067a4c3..a34a50c 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
@@ -101,11 +102,13 @@
// Decides when dark theme is optimal for this wallpaper
private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
// Minimum mean luminosity that an image needs to have to support dark text
- private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
+ private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = SystemProperties.getInt(
+ "persist.wallpapercolors.threshold", 70) / 100f;
// We also check if the image has dark pixels in it,
// to avoid bright images with some dark spots.
private static final float DARK_PIXEL_CONTRAST = 5.5f;
- private static final float MAX_DARK_AREA = 0.05f;
+ private static final float MAX_DARK_AREA = SystemProperties.getInt(
+ "persist.wallpapercolors.max_dark_area", 5) / 100f;
private final List<Color> mMainColors;
private final Map<Integer, Integer> mAllColors;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index a2cacc5..ff17824 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1499,6 +1499,13 @@
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
* wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ *
+ * <p>
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * this method will return {@code null} if the caller doesn't otherwise have
+ * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
+ * </p>
*/
public WallpaperInfo getWallpaperInfo() {
return getWallpaperInfoForUser(mContext.getUserId());
@@ -1520,6 +1527,13 @@
* wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
* returns null.
*
+ * <p>
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * this method will return {@code null} if the caller doesn't otherwise have
+ * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
+ * </p>
+ *
* @param which Specifies wallpaper to request (home or lock).
* @throws IllegalArgumentException if {@code which} is not exactly one of
* {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
index 0513099..4c97efb 100644
--- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -544,9 +544,9 @@
pw.print("blockNonMatchingNetworks=");
pw.println(mShouldBlockNonMatchingNetworks);
pw.print("includedUids=");
- pw.println(mIncludedUids);
+ pw.println(Arrays.toString(mIncludedUids));
pw.print("excludedUids=");
- pw.println(mExcludedUids);
+ pw.println(Arrays.toString(mExcludedUids));
}
@Override
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index b100eb2..c760298 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -50,8 +50,8 @@
/**
* Determines when over-the-air system updates are installed on a device. Only a device policy
- * controller (DPC) running in device owner mode can set an update policy for the device—by calling
- * the {@code DevicePolicyManager} method
+ * controller (DPC) running in device owner mode or in profile owner mode for an organization-owned
+ * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method
* {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
* policy affects the pending system update (if there is one) and any future updates for the device.
*
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 64dcc4d..3d76b28 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -17,6 +17,7 @@
package android.content;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -26,6 +27,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -101,19 +103,22 @@
@UnsupportedAppUsage
boolean mFinished;
String mReceiverClassName;
+ final int mSentFromUid;
+ final String mSentFromPackage;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
boolean ordered, boolean sticky, IBinder token, int userId, int flags) {
this(resultCode, resultData, resultExtras, type, ordered, sticky,
- guessAssumeDelivered(type, ordered), token, userId, flags);
+ guessAssumeDelivered(type, ordered), token, userId, flags,
+ Process.INVALID_UID, null);
}
/** @hide */
public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
- int userId, int flags) {
+ int userId, int flags, int sentFromUid, String sentFromPackage) {
mResultCode = resultCode;
mResultData = resultData;
mResultExtras = resultExtras;
@@ -124,6 +129,8 @@
mToken = token;
mSendingUser = userId;
mFlags = flags;
+ mSentFromUid = sentFromUid;
+ mSentFromPackage = sentFromPackage;
}
/** @hide */
@@ -322,6 +329,16 @@
return mSendingUser;
}
+ /** @hide */
+ public int getSentFromUid() {
+ return mSentFromUid;
+ }
+
+ /** @hide */
+ public String getSentFromPackage() {
+ return mSentFromPackage;
+ }
+
void checkSynchronousHint() {
// Note that we don't assert when receiving the initial sticky value,
// since that may have come from an ordered broadcast. We'll catch
@@ -687,6 +704,26 @@
}
/**
+ * Returns the uid of the app that initially sent this broadcast.
+ *
+ * @return the uid of the broadcasting app or {@link Process#INVALID_UID} if the current
+ * receiver cannot access the identity of the broadcasting app
+ */
+ public int getSentFromUid() {
+ return mPendingResult != null ? mPendingResult.mSentFromUid : Process.INVALID_UID;
+ }
+
+ /**
+ * Returns the package name of the app that initially sent this broadcast.
+ *
+ * @return the package name of the broadcasting app or {@code null} if the current
+ * receiver cannot access the identity of the broadcasting app
+ */
+ public @Nullable String getSentFromPackage() {
+ return mPendingResult != null ? mPendingResult.mSentFromPackage : null;
+ }
+
+ /**
* Control inclusion of debugging help for mismatched
* calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 387e8ae..a6e074c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2445,13 +2445,12 @@
* @see #sendBroadcast(Intent)
* @see #sendOrderedBroadcast(Intent, String)
* @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
- * @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- @SystemApi
- public abstract void sendBroadcast(Intent intent,
+ public void sendBroadcast(@NonNull Intent intent,
@Nullable String receiverPermission,
- @Nullable Bundle options);
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Like {@link #sendBroadcast(Intent, String)}, but also allows specification
@@ -2487,6 +2486,32 @@
@Nullable String receiverPermission);
/**
+ * Broadcast the given intent to all interested BroadcastReceivers, delivering
+ * them one at a time to allow more preferred receivers to consume the
+ * broadcast before it is delivered to less preferred receivers. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission (optional) String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission,
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Version of {@link #sendBroadcast(Intent)} that allows you to
* receive data back from the broadcast. This is accomplished by
* supplying your own BroadcastReceiver when calling, which will be
@@ -2572,14 +2597,13 @@
* @see android.content.BroadcastReceiver
* @see #registerReceiver
* @see android.app.Activity#RESULT_OK
- * @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- @SystemApi
- public abstract void sendOrderedBroadcast(@NonNull Intent intent,
+ public void sendOrderedBroadcast(@NonNull Intent intent,
@Nullable String receiverPermission, @Nullable Bundle options,
@Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
- int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras);
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 9027e2e..a103128 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
@@ -537,10 +538,8 @@
mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
}
- /** @hide */
- @SystemApi
@Override
- public void sendBroadcast(Intent intent, @Nullable String receiverPermission,
+ public void sendBroadcast(@NonNull Intent intent, @Nullable String receiverPermission,
@Nullable Bundle options) {
mBase.sendBroadcast(intent, receiverPermission, options);
}
@@ -557,6 +556,14 @@
mBase.sendOrderedBroadcast(intent, receiverPermission);
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @Override
+ public void sendOrderedBroadcast(@NonNull Intent intent,
+ @Nullable String receiverPermission,
+ @Nullable Bundle options) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, options);
+ }
+
@Override
public void sendOrderedBroadcast(
Intent intent, @Nullable String receiverPermission,
@@ -567,11 +574,9 @@
initialData, initialExtras);
}
- /** @hide */
- @SystemApi
@Override
public void sendOrderedBroadcast(
- Intent intent, @Nullable String receiverPermission, @Nullable Bundle options,
+ @NonNull Intent intent, @Nullable String receiverPermission, @Nullable Bundle options,
@Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
mBase.sendOrderedBroadcast(intent, receiverPermission,
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 68b0631..fd73719 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1138,6 +1138,34 @@
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
@@ -1285,6 +1313,18 @@
public static final long OVERRIDE_ANY_ORIENTATION = 265464455L;
/**
+ * When enabled, activates OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+ * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+ * only when an app is connected to the camera. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for more context.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA = 265456536L;
+
+ /**
* This override fixes display orientation to landscape natural orientation when a task is
* fullscreen. While display rotation is fixed to landscape, the orientation requested by the
* activity will be still respected by bounds resolution logic. For instance, if an activity
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e747a6f..45100be 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1060,7 +1060,7 @@
PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
InstallConstraintsResult.class);
try {
- if (result.isAllConstraintsSatisfied()) {
+ if (result.areAllConstraintsSatisfied()) {
session.commit(statusReceiver, false);
} else {
// timeout
@@ -2042,7 +2042,7 @@
*/
@SystemApi
@NonNull
- public InstallInfo getInstallInfo(@NonNull File file, int flags)
+ public InstallInfo readInstallInfo(@NonNull File file, int flags)
throws PackageParsingException {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
@@ -2616,8 +2616,17 @@
installFlags |= PackageManager.INSTALL_FORCE_PERMISSION_PROMPT;
}
- /** {@hide} */
- @SystemApi
+ /**
+ * Requests that the system not kill any of the package's running
+ * processes as part of a {@link SessionParams#MODE_INHERIT_EXISTING}
+ * session in which splits being added. By default, all installs will
+ * result in the package's running processes being killed before the
+ * install completes.
+ *
+ * @param dontKillApp set to {@code true} to request that the processes
+ * belonging to the package not be killed as part of
+ * this install.
+ */
public void setDontKillApp(boolean dontKillApp) {
if (dontKillApp) {
installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
@@ -3456,10 +3465,7 @@
/**
* Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
- *
- * @hide
*/
- @SystemApi
public boolean getDontKillApp() {
return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
}
@@ -3740,7 +3746,7 @@
/**
* Returns whether this session has requested user pre-approval.
*/
- public @NonNull boolean getIsPreApprovalRequested() {
+ public boolean isPreApprovalRequested() {
return isPreapprovalRequested;
}
@@ -4110,12 +4116,20 @@
* The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
*/
@DataClass(genParcelable = true, genHiddenConstructor = true)
+ @DataClass.Suppress("isAllConstraintsSatisfied")
public static final class InstallConstraintsResult implements Parcelable {
/**
* True if all constraints are satisfied.
*/
private boolean mAllConstraintsSatisfied;
+ /**
+ * True if all constraints are satisfied.
+ */
+ public boolean areAllConstraintsSatisfied() {
+ return mAllConstraintsSatisfied;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -4146,14 +4160,6 @@
// onConstructed(); // You can define this method to get a callback
}
- /**
- * True if all constraints are satisfied.
- */
- @DataClass.Generated.Member
- public boolean isAllConstraintsSatisfied() {
- return mAllConstraintsSatisfied;
- }
-
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -4199,10 +4205,10 @@
};
@DataClass.Generated(
- time = 1668650523745L,
+ time = 1675135664641L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ inputSignatures = "private boolean mAllConstraintsSatisfied\npublic boolean areAllConstraintsSatisfied()\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
@@ -4241,41 +4247,41 @@
*/
@NonNull
public static final InstallConstraints GENTLE_UPDATE =
- new Builder().requireAppNotInteracting().build();
+ new Builder().setAppNotInteractingRequired().build();
- private final boolean mRequireDeviceIdle;
- private final boolean mRequireAppNotForeground;
- private final boolean mRequireAppNotInteracting;
- private final boolean mRequireAppNotTopVisible;
- private final boolean mRequireNotInCall;
+ private final boolean mDeviceIdleRequired;
+ private final boolean mAppNotForegroundRequired;
+ private final boolean mAppNotInteractingRequired;
+ private final boolean mAppNotTopVisibleRequired;
+ private final boolean mNotInCallRequired;
/**
* Builder class for constructing {@link InstallConstraints}.
*/
public static final class Builder {
- private boolean mRequireDeviceIdle;
- private boolean mRequireAppNotForeground;
- private boolean mRequireAppNotInteracting;
- private boolean mRequireAppNotTopVisible;
- private boolean mRequireNotInCall;
+ private boolean mDeviceIdleRequired;
+ private boolean mAppNotForegroundRequired;
+ private boolean mAppNotInteractingRequired;
+ private boolean mAppNotTopVisibleRequired;
+ private boolean mNotInCallRequired;
/**
* This constraint requires the device is idle.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireDeviceIdle() {
- mRequireDeviceIdle = true;
+ public Builder setDeviceIdleRequired() {
+ mDeviceIdleRequired = true;
return this;
}
/**
* This constraint requires the app in question is not in the foreground.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotForeground() {
- mRequireAppNotForeground = true;
+ public Builder setAppNotForegroundRequired() {
+ mAppNotForegroundRequired = true;
return this;
}
@@ -4288,10 +4294,10 @@
* <li>being visible to the user</li>
* </ul>
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotInteracting() {
- mRequireAppNotInteracting = true;
+ public Builder setAppNotInteractingRequired() {
+ mAppNotInteractingRequired = true;
return this;
}
@@ -4300,25 +4306,27 @@
* A top-visible app is showing UI at the top of the screen that the user is
* interacting with.
*
- * Note this constraint is a subset of {@link #requireAppNotForeground()}
+ * Note this constraint is a subset of {@link #setAppNotForegroundRequired()}
* because a top-visible app is also a foreground app. This is also a subset
- * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+ * of {@link #setAppNotInteractingRequired()} because a top-visible app is interacting
* with the user.
+ *
+ * @see ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotTopVisible() {
- mRequireAppNotTopVisible = true;
+ public Builder setAppNotTopVisibleRequired() {
+ mAppNotTopVisibleRequired = true;
return this;
}
/**
* This constraint requires there is no ongoing call in the device.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireNotInCall() {
- mRequireNotInCall = true;
+ public Builder setNotInCallRequired() {
+ mNotInCallRequired = true;
return this;
}
@@ -4327,8 +4335,8 @@
*/
@NonNull
public InstallConstraints build() {
- return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
- mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+ return new InstallConstraints(mDeviceIdleRequired, mAppNotForegroundRequired,
+ mAppNotInteractingRequired, mAppNotTopVisibleRequired, mNotInCallRequired);
}
}
@@ -4354,43 +4362,43 @@
*/
@DataClass.Generated.Member
public InstallConstraints(
- boolean requireDeviceIdle,
- boolean requireAppNotForeground,
- boolean requireAppNotInteracting,
- boolean requireAppNotTopVisible,
- boolean requireNotInCall) {
- this.mRequireDeviceIdle = requireDeviceIdle;
- this.mRequireAppNotForeground = requireAppNotForeground;
- this.mRequireAppNotInteracting = requireAppNotInteracting;
- this.mRequireAppNotTopVisible = requireAppNotTopVisible;
- this.mRequireNotInCall = requireNotInCall;
+ boolean deviceIdleRequired,
+ boolean appNotForegroundRequired,
+ boolean appNotInteractingRequired,
+ boolean appNotTopVisibleRequired,
+ boolean notInCallRequired) {
+ this.mDeviceIdleRequired = deviceIdleRequired;
+ this.mAppNotForegroundRequired = appNotForegroundRequired;
+ this.mAppNotInteractingRequired = appNotInteractingRequired;
+ this.mAppNotTopVisibleRequired = appNotTopVisibleRequired;
+ this.mNotInCallRequired = notInCallRequired;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
- public boolean isRequireDeviceIdle() {
- return mRequireDeviceIdle;
+ public boolean isDeviceIdleRequired() {
+ return mDeviceIdleRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotForeground() {
- return mRequireAppNotForeground;
+ public boolean isAppNotForegroundRequired() {
+ return mAppNotForegroundRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotInteracting() {
- return mRequireAppNotInteracting;
+ public boolean isAppNotInteractingRequired() {
+ return mAppNotInteractingRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotTopVisible() {
- return mRequireAppNotTopVisible;
+ public boolean isAppNotTopVisibleRequired() {
+ return mAppNotTopVisibleRequired;
}
@DataClass.Generated.Member
- public boolean isRequireNotInCall() {
- return mRequireNotInCall;
+ public boolean isNotInCallRequired() {
+ return mNotInCallRequired;
}
@Override
@@ -4406,11 +4414,11 @@
InstallConstraints that = (InstallConstraints) o;
//noinspection PointlessBooleanExpression
return true
- && mRequireDeviceIdle == that.mRequireDeviceIdle
- && mRequireAppNotForeground == that.mRequireAppNotForeground
- && mRequireAppNotInteracting == that.mRequireAppNotInteracting
- && mRequireAppNotTopVisible == that.mRequireAppNotTopVisible
- && mRequireNotInCall == that.mRequireNotInCall;
+ && mDeviceIdleRequired == that.mDeviceIdleRequired
+ && mAppNotForegroundRequired == that.mAppNotForegroundRequired
+ && mAppNotInteractingRequired == that.mAppNotInteractingRequired
+ && mAppNotTopVisibleRequired == that.mAppNotTopVisibleRequired
+ && mNotInCallRequired == that.mNotInCallRequired;
}
@Override
@@ -4420,11 +4428,11 @@
// int fieldNameHashCode() { ... }
int _hash = 1;
- _hash = 31 * _hash + Boolean.hashCode(mRequireDeviceIdle);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotForeground);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotInteracting);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotTopVisible);
- _hash = 31 * _hash + Boolean.hashCode(mRequireNotInCall);
+ _hash = 31 * _hash + Boolean.hashCode(mDeviceIdleRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotForegroundRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotInteractingRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotTopVisibleRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mNotInCallRequired);
return _hash;
}
@@ -4435,11 +4443,11 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
- if (mRequireDeviceIdle) flg |= 0x1;
- if (mRequireAppNotForeground) flg |= 0x2;
- if (mRequireAppNotInteracting) flg |= 0x4;
- if (mRequireAppNotTopVisible) flg |= 0x8;
- if (mRequireNotInCall) flg |= 0x10;
+ if (mDeviceIdleRequired) flg |= 0x1;
+ if (mAppNotForegroundRequired) flg |= 0x2;
+ if (mAppNotInteractingRequired) flg |= 0x4;
+ if (mAppNotTopVisibleRequired) flg |= 0x8;
+ if (mNotInCallRequired) flg |= 0x10;
dest.writeByte(flg);
}
@@ -4455,17 +4463,17 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
- boolean requireDeviceIdle = (flg & 0x1) != 0;
- boolean requireAppNotForeground = (flg & 0x2) != 0;
- boolean requireAppNotInteracting = (flg & 0x4) != 0;
- boolean requireAppNotTopVisible = (flg & 0x8) != 0;
- boolean requireNotInCall = (flg & 0x10) != 0;
+ boolean deviceIdleRequired = (flg & 0x1) != 0;
+ boolean appNotForegroundRequired = (flg & 0x2) != 0;
+ boolean appNotInteractingRequired = (flg & 0x4) != 0;
+ boolean appNotTopVisibleRequired = (flg & 0x8) != 0;
+ boolean notInCallRequired = (flg & 0x10) != 0;
- this.mRequireDeviceIdle = requireDeviceIdle;
- this.mRequireAppNotForeground = requireAppNotForeground;
- this.mRequireAppNotInteracting = requireAppNotInteracting;
- this.mRequireAppNotTopVisible = requireAppNotTopVisible;
- this.mRequireNotInCall = requireNotInCall;
+ this.mDeviceIdleRequired = deviceIdleRequired;
+ this.mAppNotForegroundRequired = appNotForegroundRequired;
+ this.mAppNotInteractingRequired = appNotInteractingRequired;
+ this.mAppNotTopVisibleRequired = appNotTopVisibleRequired;
+ this.mNotInCallRequired = notInCallRequired;
// onConstructed(); // You can define this method to get a callback
}
@@ -4485,10 +4493,10 @@
};
@DataClass.Generated(
- time = 1670207178734L,
+ time = 1675135664653L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mDeviceIdleRequired\nprivate final boolean mAppNotForegroundRequired\nprivate final boolean mAppNotInteractingRequired\nprivate final boolean mAppNotTopVisibleRequired\nprivate final boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mDeviceIdleRequired\nprivate boolean mAppNotForegroundRequired\nprivate boolean mAppNotInteractingRequired\nprivate boolean mAppNotTopVisibleRequired\nprivate boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index da847a8..19719a8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -2387,9 +2387,8 @@
}
//TODO: Do we need to treat this as error?
- if (!mDeviceStatus.containsKey(id) || !isAvailable(mDeviceStatus.get(id))
- || !mUnavailablePhysicalDevices.containsKey(id)) {
- Log.e(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ if (!mDeviceStatus.containsKey(id) || !mUnavailablePhysicalDevices.containsKey(id)) {
+ Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera "
+ "status change", id));
return;
}
@@ -2414,6 +2413,12 @@
return;
}
+ if (!isAvailable(mDeviceStatus.get(id))) {
+ Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ + "status change callback(s)", id));
+ return;
+ }
+
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
Executor executor = mCallbackMap.valueAt(i);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index d13a97d..d49cc44 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -115,6 +115,23 @@
"android.hardware.display.category.PRESENTATION";
/**
+ * Display category: Rear displays.
+ * <p>
+ * This category can be used to identify complementary internal displays that are facing away
+ * from the user.
+ * Certain applications may present to this display.
+ * Similar to presentation displays.
+ * </p>
+ *
+ * @see android.app.Presentation
+ * @see Display#FLAG_PRESENTATION
+ * @see #getDisplays(String)
+ * @hide
+ */
+ public static final String DISPLAY_CATEGORY_REAR =
+ "android.hardware.display.category.REAR";
+
+ /**
* Display category: All displays, including disabled displays.
* <p>
* This returns all displays, including currently disabled and inaccessible displays.
@@ -619,11 +636,19 @@
synchronized (mLock) {
try {
if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL,
+ Display.FLAG_PRESENTATION);
+ } else if (DISPLAY_CATEGORY_REAR.equals(category)) {
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL,
+ Display.FLAG_REAR);
} else if (category == null
|| DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
addAllDisplaysLocked(mTempDisplays, displayIds);
@@ -644,15 +669,16 @@
}
}
- private void addPresentationDisplaysLocked(
- ArrayList<Display> displays, int[] displayIds, int matchType) {
- for (int i = 0; i < displayIds.length; i++) {
- if (displayIds[i] == DEFAULT_DISPLAY) {
+ private void addDisplaysLocked(
+ ArrayList<Display> displays, int[] displayIds, int matchType, int flagMask) {
+ for (int displayId : displayIds) {
+ if (displayId == DEFAULT_DISPLAY) {
continue;
}
- Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+
+ Display display = getOrCreateDisplayLocked(displayId, /* assumeValid= */ true);
if (display != null
- && (display.getFlags() & Display.FLAG_PRESENTATION) != 0
+ && (display.getFlags() & flagMask) == flagMask
&& display.getType() == matchType) {
displays.add(display);
}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 222e88f..0a7eb43 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -69,7 +70,10 @@
* An interface describing the callback for bugreport progress and status.
*
* <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a
- * terminal call to either {@link #onFinished} or {@link #onError}.
+ * terminal call to either {@link #onFinished} or {@link #onError}. Note that
+ * {@link #onFinished(String)} will only be invoked when calling {@code startBugreport} with the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag set. Otherwise,
+ * {@link #onFinished()} will be invoked.
*
* <p>If an issue is encountered while starting the bugreport asynchronously, callers will
* receive an {@link #onError} call without any {@link #onProgress} callbacks.
@@ -88,7 +92,8 @@
BUGREPORT_ERROR_RUNTIME,
BUGREPORT_ERROR_USER_DENIED_CONSENT,
BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
- BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
+ BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS,
+ BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE
})
public @interface BugreportErrorCode {}
@@ -115,6 +120,10 @@
public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
+ /** There is no bugreport to retrieve for the caller. */
+ public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE =
+ IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE;
+
/**
* Called when there is a progress update.
*
@@ -137,9 +146,25 @@
*/
public void onError(@BugreportErrorCode int errorCode) {}
- /** Called when taking bugreport finishes successfully. */
+ /** Called when taking bugreport finishes successfully.
+ *
+ * <p>This callback will be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. Otherwise, the
+ * {@link #onFinished(String)} callback will be invoked.
+ */
public void onFinished() {}
+ /** Called when taking bugreport finishes successfully.
+ *
+ * <p>This callback will only be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the
+ * {@link #onFinished()} callback will be invoked.
+ *
+ * @param bugreportFile the absolute path of the generated bugreport file.
+
+ */
+ public void onFinished(@NonNull String bugreportFile) {}
+
/**
* Called when it is ready for calling app to show UI, showing any extra UI before this
* callback can interfere with bugreport generation.
@@ -178,7 +203,9 @@
* updates.
*
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the user
- * consents to sharing with the calling app.
+ * consents to sharing with the calling app. If
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred
+ * and no files will be copied to the given file descriptors.
*
* <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
*
@@ -205,7 +232,9 @@
Preconditions.checkNotNull(executor);
Preconditions.checkNotNull(callback);
- boolean isScreenshotRequested = screenshotFd != null;
+ boolean deferConsent =
+ (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+ boolean isScreenshotRequested = screenshotFd != null || deferConsent;
if (screenshotFd == null) {
// Binder needs a valid File Descriptor to be passed
screenshotFd =
@@ -213,7 +242,7 @@
new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
}
DumpstateListener dsListener =
- new DumpstateListener(executor, callback, isScreenshotRequested);
+ new DumpstateListener(executor, callback, isScreenshotRequested, deferConsent);
// Note: mBinder can get callingUid from the binder transaction.
mBinder.startBugreport(
-1 /* callingUid */,
@@ -238,6 +267,58 @@
}
/**
+ * Retrieves a previously generated bugreport.
+ *
+ * <p>The previously generated bugreport must have been generated by calling {@link
+ * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams,
+ * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT}
+ * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)}
+ * callback for a previously generated bugreport must be passed to this method. A caller may
+ * only retrieve bugreports that they have previously requested.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
+ * consents to sharing with the calling app.
+ *
+ * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
+ *
+ * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
+ * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
+ *
+ * @param bugreportFile the identifier for a bugreport that was previously generated for this
+ * caller using {@code startBugreport}.
+ * @param bugreportFd file to copy over the previous bugreport. This should be opened in
+ * write-only, append mode.
+ * @param executor the executor to execute callback methods.
+ * @param callback callback for progress and status updates.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ @WorkerThread
+ public void retrieveBugreport(
+ @NonNull String bugreportFile,
+ @NonNull ParcelFileDescriptor bugreportFd,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback
+ ) {
+ try {
+ Preconditions.checkNotNull(bugreportFile);
+ Preconditions.checkNotNull(bugreportFd);
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(callback);
+ DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
+ mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+ bugreportFd.getFileDescriptor(),
+ bugreportFile,
+ dsListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ IoUtils.closeQuietly(bugreportFd);
+ }
+ }
+
+ /**
* Starts a connectivity bugreport.
*
* <p>The connectivity bugreport is a specialized version of bugreport that only includes
@@ -316,7 +397,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.DUMP)
+ @RequiresPermission(Manifest.permission.DUMP)
public void requestBugreport(
@NonNull BugreportParams params,
@Nullable CharSequence shareTitle,
@@ -335,12 +416,15 @@
private final Executor mExecutor;
private final BugreportCallback mCallback;
private final boolean mIsScreenshotRequested;
+ private final boolean mIsConsentDeferred;
DumpstateListener(
- Executor executor, BugreportCallback callback, boolean isScreenshotRequested) {
+ Executor executor, BugreportCallback callback, boolean isScreenshotRequested,
+ boolean isConsentDeferred) {
mExecutor = executor;
mCallback = callback;
mIsScreenshotRequested = isScreenshotRequested;
+ mIsConsentDeferred = isConsentDeferred;
}
@Override
@@ -364,10 +448,14 @@
}
@Override
- public void onFinished() throws RemoteException {
+ public void onFinished(String bugreportFile) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() -> mCallback.onFinished());
+ if (mIsConsentDeferred) {
+ mExecutor.execute(() -> mCallback.onFinished(bugreportFile));
+ } else {
+ mExecutor.execute(() -> mCallback.onFinished());
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 990883f..d9d14b0 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Parameters that specify what kind of bugreport should be taken.
@@ -125,7 +126,8 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
- BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
+ BUGREPORT_FLAG_DEFER_CONSENT
})
public @interface BugreportFlag {}
@@ -135,4 +137,14 @@
*/
public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
+
+ /**
+ * Flag for deferring user consent.
+ *
+ * <p>This flag should be used in cases where it may not be possible for the user to respond
+ * to a consent dialog immediately, such as when the user is driving. The generated bugreport
+ * may be retrieved at a later time using {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ */
+ public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 25863a6..39ea2fd 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -360,6 +360,17 @@
public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 12;
/**
+ * Display flag: Indicates that the display is a rear display.
+ * <p>
+ * This flag identifies complementary displays that are facing away from the user.
+ * </p>
+ *
+ * @hide
+ * @see #getFlags()
+ */
+ public static final int FLAG_REAR = 1 << 13;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index c2b6bc5..e26c7be 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -906,6 +906,9 @@
if ((flags & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
result.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
}
+ if ((flags & Display.FLAG_REAR) != 0) {
+ result.append(", FLAG_REAR_DISPLAY");
+ }
return result.toString();
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a5693f3..06dab15 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8919,7 +8919,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8927,6 +8928,7 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15964,7 +15966,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -26173,7 +26176,11 @@
getLocationInWindow(outLocation);
final AttachInfo info = mAttachInfo;
- if (info != null) {
+
+ // Need to offset the outLocation with the window bounds, but only if "Sandboxing View
+ // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation}
+ // within activity bounds.
+ if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index f51d9ba..c96d298 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -27,6 +27,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
@@ -232,6 +233,12 @@
*/
private static final int MAXIMUM_FLING_VELOCITY = 8000;
+ /** Value used as a minimum fling velocity, when fling is not supported. */
+ private static final int NO_FLING_MIN_VELOCITY = Integer.MAX_VALUE;
+
+ /** Value used as a maximum fling velocity, when fling is not supported. */
+ private static final int NO_FLING_MAX_VELOCITY = Integer.MIN_VALUE;
+
/**
* Delay before dispatching a recurring accessibility event in milliseconds.
* This delay guarantees that a recurring event will be send at most once
@@ -333,6 +340,8 @@
private final int mFadingEdgeLength;
private final int mMinimumFlingVelocity;
private final int mMaximumFlingVelocity;
+ private final int mMinimumRotaryEncoderFlingVelocity;
+ private final int mMaximumRotaryEncoderFlingVelocity;
private final int mScrollbarSize;
private final int mTouchSlop;
private final int mHandwritingSlop;
@@ -378,6 +387,8 @@
mFadingEdgeLength = FADING_EDGE_LENGTH;
mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
+ mMinimumRotaryEncoderFlingVelocity = MINIMUM_FLING_VELOCITY;
+ mMaximumRotaryEncoderFlingVelocity = MAXIMUM_FLING_VELOCITY;
mScrollbarSize = SCROLL_BAR_SIZE;
mTouchSlop = TOUCH_SLOP;
mHandwritingSlop = HANDWRITING_SLOP;
@@ -504,6 +515,19 @@
com.android.internal.R.dimen.config_viewMinFlingVelocity);
mMaximumFlingVelocity = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+
+ int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
+ mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
+ mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
+ } else {
+ mMinimumRotaryEncoderFlingVelocity = configMinRotaryEncoderFlingVelocity;
+ mMaximumRotaryEncoderFlingVelocity = configMaxRotaryEncoderFlingVelocity;
+ }
+
mGlobalActionsKeyTimeout = res.getInteger(
com.android.internal.R.integer.config_globalActionsKeyTimeout);
@@ -1077,6 +1101,98 @@
}
/**
+ * Minimum absolute value of velocity to initiate a fling for a motion generated by an
+ * {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and on
+ * a given motion event {@code axis}.
+ *
+ * <p>Before utilizing this method to get a minimum fling velocity for a motion generated by the
+ * input device, scale the velocity of the motion events generated by the input device to pixels
+ * per second.
+ *
+ * <p>For instance, if you tracked {@link MotionEvent#AXIS_SCROLL} vertical velocities generated
+ * from a {@link InputDevice#SOURCE_ROTARY_ENCODER}, the velocity returned from
+ * {@link VelocityTracker} will be in the units with which the axis values were reported in the
+ * motion event. Before comparing that velocity against the minimum fling velocity specified
+ * here, make sure that the {@link MotionEvent#AXIS_SCROLL} velocity from the tracker is
+ * calculated in "units per second" (see {@link VelocityTracker#computeCurrentVelocity(int)},
+ * {@link VelocityTracker#computeCurrentVelocity(int, float)} to adjust your velocity
+ * computations to "per second"), and use {@link #getScaledVerticalScrollFactor} to change this
+ * velocity value to "pixels/second".
+ *
+ * <p>If the provided {@code inputDeviceId} is not valid, or if the input device whose ID is
+ * provided does not support the given motion event source and/or axis, this method will return
+ * {@code Integer.MAX_VALUE}.
+ *
+ * <h3>Obtaining the correct arguments for this method call</h3>
+ * <p><b>inputDeviceId</b>: if calling this method in response to a {@link MotionEvent}, use
+ * the device ID that is reported by the event, which can be obtained using
+ * {@link MotionEvent#getDeviceId()}. Otherwise, use a valid ID that is obtained from
+ * {@link InputDevice#getId()}, or from an {@link InputManager} instance
+ * ({@link InputManager#getInputDeviceIds()} gives all the valid input device IDs).
+ *
+ * <p><b>axis</b>: a {@link MotionEvent} may report data for multiple axes, and each axis may
+ * have multiple data points for different pointers. Use the axis for which you obtained the
+ * velocity for ({@link VelocityTracker} lets you calculate velocities for a specific axis. Use
+ * the axis for which you calculated velocity). You can use
+ * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
+ * {@link InputDevice}, from which you can derive all the valid axes for the device.
+ *
+ * <p><b>source</b>: use {@link MotionEvent#getSource()} if calling this method in response to a
+ * {@link MotionEvent}. Otherwise, use a valid source for the {@link InputDevice}. You can use
+ * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
+ * {@link InputDevice}, from which you can derive all the valid sources for the device.
+ *
+ *
+ * <p>This method optimizes calls over multiple input device IDs, so caching the return value of
+ * the method is not necessary if you are handling multiple input devices.
+ *
+ * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
+ * fling.
+ * @param axis the axis on which the motion triggering the fling happened. This axis should be
+ * a valid axis that can be reported by the provided input device from the provided
+ * input device source.
+ * @param source the input source of the motion causing fling. This source should be a valid
+ * source for the {@link InputDevice} whose ID is {@code inputDeviceId}.
+ *
+ * @return the minimum velocity, in pixels/second, to trigger fling.
+ *
+ * @see InputDevice#getMotionRange(int, int)
+ * @see InputDevice#getMotionRanges()
+ * @see VelocityTracker#getAxisVelocity(int, int)
+ * @see VelocityTracker#getAxisVelocity(int)
+ */
+ public int getScaledMinimumFlingVelocity(int inputDeviceId, int axis, int source) {
+ if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return NO_FLING_MIN_VELOCITY;
+
+ if (source == InputDevice.SOURCE_ROTARY_ENCODER) return mMinimumRotaryEncoderFlingVelocity;
+
+ return mMinimumFlingVelocity;
+ }
+
+ /**
+ * Maximum absolute value of velocity to initiate a fling for a motion generated by an
+ * {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and on
+ * a given motion event {@code axis}.
+ *
+ * <p>Similar to {@link #getScaledMinimumFlingVelocity(int, int, int)}, but for maximum fling
+ * velocity, instead of minimum. Also, unlike that method which returns
+ * {@code Integer.MAX_VALUE} for bad input device ID, source and/or motion event axis inputs,
+ * this method returns {@code Integer.MIN_VALUE} for such bad inputs.
+ */
+ public int getScaledMaximumFlingVelocity(int inputDeviceId, int axis, int source) {
+ if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return NO_FLING_MAX_VELOCITY;
+
+ if (source == InputDevice.SOURCE_ROTARY_ENCODER) return mMaximumRotaryEncoderFlingVelocity;
+
+ return mMaximumFlingVelocity;
+ }
+
+ private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
+ InputDevice device = InputManager.getInstance().getInputDevice(id);
+ return device != null && device.getMotionRange(axis, source) != null;
+ }
+
+ /**
* Check if shortcuts should be displayed in menus.
*
* @return {@code True} if shortcuts should be displayed in menus.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index de946a4..cb316e2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -79,6 +80,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -98,6 +100,7 @@
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -709,6 +712,7 @@
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ private final Rect mLastLayoutFrame;
Rect mOverrideInsetsFrame;
final Rect mPendingBackDropFrame = new Rect();
@@ -888,6 +892,15 @@
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -951,6 +964,7 @@
mHeight = -1;
mDirty = new Rect();
mWinFrame = new Rect();
+ mLastLayoutFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
@@ -978,6 +992,8 @@
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -1315,7 +1331,7 @@
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
registerBackCallbackOnWindow();
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
@@ -1842,7 +1858,7 @@
onMovedToDisplay(displayId, mLastConfigurationFromResources);
}
- setFrame(frame);
+ setFrame(frame, false /* withinRelayout */);
mTmpFrames.displayFrame.set(displayFrame);
if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
mTmpFrames.attachedFrame.set(attachedFrame);
@@ -5836,7 +5852,7 @@
mTmpFrames.frame.right = l + w;
mTmpFrames.frame.top = t;
mTmpFrames.frame.bottom = t + h;
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, false /* withinRelayout */);
maybeHandleWindowMove(mWinFrame);
}
break;
@@ -8343,7 +8359,7 @@
// If the position and the size of the frame are both changed, it will trigger a BLAST
// sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
// need to send attributes via relayoutAsync.
- final Rect oldFrame = mWinFrame;
+ final Rect oldFrame = mLastLayoutFrame;
final Rect newFrame = mTmpFrames.frame;
final boolean positionChanged =
newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
@@ -8473,7 +8489,7 @@
params.restore();
}
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
return relayoutResult;
}
@@ -8508,8 +8524,18 @@
mIsSurfaceOpaque = opaque;
}
- private void setFrame(Rect frame) {
+ /**
+ * Set the mWinFrame of this window.
+ * @param frame the new frame of this window.
+ * @param withinRelayout {@code true} if this setting is within the relayout, or is the initial
+ * setting. That will make sure in the relayout process, we always compare
+ * the window frame with the last processed window frame.
+ */
+ private void setFrame(Rect frame, boolean withinRelayout) {
mWinFrame.set(frame);
+ if (withinRelayout) {
+ mLastLayoutFrame.set(frame);
+ }
final WindowConfiguration winConfig = getCompatWindowConfiguration();
mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
@@ -8544,6 +8570,9 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // {@link #mTmpFrames.displayFrame} is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8560,6 +8589,60 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // {@link #mTmpFrames.displayFrame} is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (isViewBoundsSandboxingEnabled()) {
+ inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+ }
+ }
+
+ /**
+ * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
+ *
+ * <p>This is called by {@link #applyViewBoundsSandboxingIfNeeded} and
+ * {@link android.view.View#getLocationOnScreen} to check if there is a need to add
+ * {@link android.view.View.AttachInfo.mWindowLeft} and
+ * {@link android.view.View.AttachInfo.mWindowTop} offsets.
+ */
+ boolean isViewBoundsSandboxingEnabled() {
+ return mViewBoundsSandboxingEnabled;
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 145a5d3..3d3f4f6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -855,6 +855,79 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the application can be opted-in or opted-out
+ * from the compatibility treatment that enables sending a fake focus event for unfocused
+ * resumed split screen activities. This is needed because some game engines wait to get
+ * focus before drawing the content of the app which isn't guaranteed by default in multi-window
+ * modes.
+ *
+ * <p>Device manufacturers can enable this treatment using their discretion on a per-device
+ * basis to improve display compatibility. The treatment also needs to be specifically enabled
+ * on a per-app basis afterwards. This can either be done by device manufacturers or developers.
+ *
+ * <p>With this property set to {@code true}, the system will apply the treatment only if the
+ * device manufacturer had previously enabled it on the device. A fake focus event will be sent
+ * to the app after it is resumed only if the app is in split-screen.
+ *
+ * <p>Setting this property to {@code false} informs the system that the activity must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ * <p>If the property remains unset the system will apply the treatment only if it had
+ * previously been enabled both at the device and app level by the device manufacturer.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the app should be excluded from the
* camera compatibility force rotation treatment.
*
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 5e323fa..8f270f5 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -361,6 +361,16 @@
* @throws NullPointerException if the target is {@code null}.
*/
@Override
+ public boolean requestCursorUpdates(@CursorUpdateMode int cursorUpdateMode,
+ @CursorUpdateFilter int cursorUpdateFilter) {
+ return mTarget.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ @Override
public void requestTextBoundsInfo(
@NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d71e82f..136846a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -187,20 +187,34 @@
// Tag used when the Editor maintains its own separate UndoManager.
private static final String UNDO_OWNER_TAG = "Editor";
- // Ordering constants used to place the Action Mode or context menu items in their menu.
- private static final int MENU_ITEM_ORDER_ASSIST = 0;
- private static final int MENU_ITEM_ORDER_UNDO = 2;
- private static final int MENU_ITEM_ORDER_REDO = 3;
- private static final int MENU_ITEM_ORDER_CUT = 4;
- private static final int MENU_ITEM_ORDER_COPY = 5;
- private static final int MENU_ITEM_ORDER_PASTE = 6;
- private static final int MENU_ITEM_ORDER_SHARE = 7;
- private static final int MENU_ITEM_ORDER_SELECT_ALL = 8;
- private static final int MENU_ITEM_ORDER_REPLACE = 9;
- private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
- private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
- private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
- private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+ // Ordering constants used to place the Action Mode items in their menu.
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_ASSIST = 0;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_CUT = 4;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_COPY = 5;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PASTE = 6;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SHARE = 7;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SELECT_ALL = 8;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_REPLACE = 9;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_AUTOFILL = 10;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+
+ // Ordering constants used to place the Context Menu items in their menu.
+ private static final int CONTEXT_MENU_ITEM_ORDER_UNDO = 2;
+ private static final int CONTEXT_MENU_ITEM_ORDER_REDO = 3;
+ private static final int CONTEXT_MENU_ITEM_ORDER_CUT = 4;
+ private static final int CONTEXT_MENU_ITEM_ORDER_COPY = 5;
+ private static final int CONTEXT_MENU_ITEM_ORDER_PASTE = 6;
+ private static final int CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 7;
+ private static final int CONTEXT_MENU_ITEM_ORDER_SELECT_ALL = 8;
+ private static final int CONTEXT_MENU_ITEM_ORDER_SHARE = 9;
+ private static final int CONTEXT_MENU_ITEM_ORDER_AUTOFILL = 10;
+ private static final int CONTEXT_MENU_ITEM_ORDER_REPLACE = 11;
+
+ private static final int CONTEXT_MENU_GROUP_UNDO_REDO = Menu.FIRST;
+ private static final int CONTEXT_MENU_GROUP_CLIPBOARD = Menu.FIRST + 1;
+ private static final int CONTEXT_MENU_GROUP_MISC = Menu.FIRST + 2;
private static final int FLAG_MISSPELLED_OR_GRAMMAR_ERROR =
SuggestionSpan.FLAG_MISSPELLED | SuggestionSpan.FLAG_GRAMMAR_ERROR;
@@ -3114,8 +3128,8 @@
for (int i = 0; i < suggestionInfoArray.length; i++) {
suggestionInfoArray[i] = new SuggestionInfo();
}
- final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, MENU_ITEM_ORDER_REPLACE,
- com.android.internal.R.string.replace);
+ final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE,
+ CONTEXT_MENU_ITEM_ORDER_REPLACE, com.android.internal.R.string.replace);
final int numItems = mSuggestionHelper.getSuggestionInfo(suggestionInfoArray, null);
for (int i = 0; i < numItems; i++) {
final SuggestionInfo info = suggestionInfoArray[i];
@@ -3133,47 +3147,51 @@
final int keyboard = mTextView.getResources().getConfiguration().keyboard;
menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
- menu.add(Menu.NONE, TextView.ID_UNDO, MENU_ITEM_ORDER_UNDO,
+ menu.setGroupDividerEnabled(true);
+
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, CONTEXT_MENU_ITEM_ORDER_UNDO,
com.android.internal.R.string.undo)
.setAlphabeticShortcut('z')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canUndo());
- menu.add(Menu.NONE, TextView.ID_REDO, MENU_ITEM_ORDER_REDO,
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, CONTEXT_MENU_ITEM_ORDER_REDO,
com.android.internal.R.string.redo)
.setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canRedo());
- menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, CONTEXT_MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCut());
- menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, CONTEXT_MENU_ITEM_ORDER_COPY,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCopy());
- menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, CONTEXT_MENU_ITEM_ORDER_PASTE,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setEnabled(mTextView.canPaste())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_PASTE_AS_PLAIN_TEXT, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
+ CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
com.android.internal.R.string.paste_as_plain_text)
.setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setEnabled(mTextView.canPasteAsPlainText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
- com.android.internal.R.string.share)
- .setEnabled(mTextView.canShare())
- .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
- com.android.internal.R.string.selectAll)
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
+ CONTEXT_MENU_ITEM_ORDER_SELECT_ALL, com.android.internal.R.string.selectAll)
.setAlphabeticShortcut('a')
.setEnabled(mTextView.canSelectAllText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
+
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, CONTEXT_MENU_ITEM_ORDER_SHARE,
+ com.android.internal.R.string.share)
+ .setEnabled(mTextView.canShare())
+ .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, CONTEXT_MENU_ITEM_ORDER_AUTOFILL,
android.R.string.autofill)
.setEnabled(mTextView.canRequestAutofill())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
@@ -4355,28 +4373,28 @@
private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
- menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
+ menu.add(Menu.NONE, TextView.ID_CUT, ACTION_MODE_MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canCopy()) {
- menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
+ menu.add(Menu.NONE, TextView.ID_COPY, ACTION_MODE_MENU_ITEM_ORDER_COPY,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canPaste()) {
- menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
+ menu.add(Menu.NONE, TextView.ID_PASTE, ACTION_MODE_MENU_ITEM_ORDER_PASTE,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canShare()) {
- menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
+ menu.add(Menu.NONE, TextView.ID_SHARE, ACTION_MODE_MENU_ITEM_ORDER_SHARE,
com.android.internal.R.string.share)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -4384,7 +4402,7 @@
if (mTextView.canRequestAutofill()) {
final String selected = mTextView.getSelectedText();
if (selected == null || selected.isEmpty()) {
- menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
+ menu.add(Menu.NONE, TextView.ID_AUTOFILL, ACTION_MODE_MENU_ITEM_ORDER_AUTOFILL,
com.android.internal.R.string.autofill)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
@@ -4394,7 +4412,7 @@
menu.add(
Menu.NONE,
TextView.ID_PASTE_AS_PLAIN_TEXT,
- MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
+ ACTION_MODE_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
com.android.internal.R.string.paste_as_plain_text)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -4421,7 +4439,7 @@
boolean canSelectAll = mTextView.canSelectAllText();
boolean selectAllItemExists = menu.findItem(TextView.ID_SELECT_ALL) != null;
if (canSelectAll && !selectAllItemExists) {
- menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
+ menu.add(Menu.NONE, TextView.ID_SELECT_ALL, ACTION_MODE_MENU_ITEM_ORDER_SELECT_ALL,
com.android.internal.R.string.selectAll)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (!canSelectAll && selectAllItemExists) {
@@ -4433,7 +4451,7 @@
boolean canReplace = mTextView.isSuggestionsEnabled() && shouldOfferToShowSuggestions();
boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
if (canReplace && !replaceItemExists) {
- menu.add(Menu.NONE, TextView.ID_REPLACE, MENU_ITEM_ORDER_REPLACE,
+ menu.add(Menu.NONE, TextView.ID_REPLACE, ACTION_MODE_MENU_ITEM_ORDER_REPLACE,
com.android.internal.R.string.replace)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (!canReplace && replaceItemExists) {
@@ -4459,12 +4477,12 @@
// Primary assist action (Always shown).
final MenuItem item = addAssistMenuItem(menu,
textClassification.getActions().get(0), TextView.ID_ASSIST,
- MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS);
+ ACTION_MODE_MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS);
item.setIntent(textClassification.getIntent());
} else if (hasLegacyAssistItem(textClassification)) {
// Legacy primary assist action (Always shown).
final MenuItem item = menu.add(
- TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
+ TextView.ID_ASSIST, TextView.ID_ASSIST, ACTION_MODE_MENU_ITEM_ORDER_ASSIST,
textClassification.getLabel())
.setIcon(textClassification.getIcon())
.setIntent(textClassification.getIntent());
@@ -4478,7 +4496,7 @@
for (int i = 1; i < count; i++) {
// Secondary assist action (Never shown).
addAssistMenuItem(menu, textClassification.getActions().get(i), Menu.NONE,
- MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1,
+ ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1,
MenuItem.SHOW_AS_ACTION_NEVER);
}
mPrevTextClassification = textClassification;
@@ -7923,7 +7941,7 @@
for (int i = 0; i < size; i++) {
final ResolveInfo resolveInfo = mSupportedActivities.get(i);
menu.add(Menu.NONE, Menu.NONE,
- Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
+ Editor.ACTION_MODE_MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
getLabel(resolveInfo))
.setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
index 1902f80..c8b7def 100644
--- a/core/java/com/android/internal/util/ScreenshotRequest.java
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -173,6 +173,9 @@
public Builder(
@WindowManager.ScreenshotType int type,
@WindowManager.ScreenshotSource int source) {
+ if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ throw new IllegalArgumentException("Invalid screenshot type requested!");
+ }
mType = type;
mSource = source;
}
diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java
index 0b3f2ae..3c4b622 100644
--- a/core/java/com/android/server/backup/ShortcutBackupHelper.java
+++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.pm.IShortcutService;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.Slog;
public class ShortcutBackupHelper extends BlobBackupHelper {
@@ -28,8 +27,11 @@
private static final String KEY_USER_FILE = "shortcutuser.xml";
- public ShortcutBackupHelper() {
+ private final int mUserId;
+
+ public ShortcutBackupHelper(int userId) {
super(BLOB_VERSION, KEY_USER_FILE);
+ mUserId = userId;
}
private IShortcutService getShortcutService() {
@@ -42,7 +44,7 @@
switch (key) {
case KEY_USER_FILE:
try {
- return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
+ return getShortcutService().getBackupPayload(mUserId);
} catch (Exception e) {
Slog.wtf(TAG, "Backup failed", e);
}
@@ -58,7 +60,7 @@
switch (key) {
case KEY_USER_FILE:
try {
- getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
+ getShortcutService().applyRestore(payload, mUserId);
} catch (Exception e) {
Slog.wtf(TAG, "Restore failed", e);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 82f414b..b79c540 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,12 @@
cppflags: ["-Wno-conversion-null"],
+ product_variables: {
+ eng: {
+ cflags: ["-DNO_RESET_STACK_PROTECTOR"],
+ },
+ },
+
cpp_std: "gnu++20",
srcs: [
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 6a92581..5965e17 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2302,7 +2302,7 @@
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
}
-#if defined(__BIONIC__)
+#if defined(__BIONIC__) && !defined(NO_RESET_STACK_PROTECTOR)
// Reset the stack guard for the new process.
android_reset_stack_guards();
#endif
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 37d5b84..1e3074c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2674,6 +2674,14 @@
<!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
<dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
+ <!-- Minimum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMinRotaryEncoderFlingVelocity">-1dp</dimen>
+
+ <!-- Maximum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen>
+
<!-- Amount of time in ms the user needs to press the relevant key to bring up the
global actions dialog -->
<integer name="config_globalActionsKeyTimeout">500</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2af91e1..11aac16 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -515,6 +515,8 @@
<java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
+ <java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
+ <java-symbol type="dimen" name="config_viewMaxRotaryEncoderFlingVelocity" />
<java-symbol type="dimen" name="config_scrollbarSize" />
<java-symbol type="dimen" name="config_horizontalScrollFactor" />
<java-symbol type="dimen" name="config_verticalScrollFactor" />
@@ -2498,6 +2500,8 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
<java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="zen_mode_alarm" />
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
index 30540a5..89acbc7 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -131,6 +131,12 @@
assertEquals(Insets.NONE, out.getInsets());
}
+ @Test
+ public void testInvalidType() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new ScreenshotRequest.Builder(5, 2).build());
+ }
+
private Bitmap makeHardwareBitmap(int width, int height) {
HardwareBuffer buffer = HardwareBuffer.create(
width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 00ffd09..8cd8ddf 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -670,8 +670,12 @@
/**
* Draws a mesh object to the screen.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param mesh {@link Mesh} object that will be drawn to the screen
- * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+ * @param blendMode {@link BlendMode} used to blend mesh primitives as the destination color
+ * with the Paint color/shader as the source color.
* @param paint {@link Paint} used to provide a color/shader/blend mode.
*/
public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2427dec..4c669b8 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -212,9 +212,9 @@
new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
- 0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+ 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true);
private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
- new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+ new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f,
2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
// See static initialization block next to #get(Named)
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index c3eb7aa..0488b9d 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -668,7 +668,7 @@
/** @hide */
public void setTargetSdrHdrRatio(float ratio) {
- if (ratio < 1.f || Float.isNaN(ratio) || Float.isInfinite(ratio)) ratio = 1.f;
+ if (ratio < 1.f || !Float.isFinite(ratio)) ratio = 1.f;
nSetTargetSdrHdrRatio(mNativeProxy, ratio);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 3adae70..87c2822 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -25,12 +25,12 @@
import android.util.ArraySet;
import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Reference implementation of androidx.window.extensions.area OEM interface for use with
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index cddbf469..7eee6da 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index a906522..e787ed9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -45,8 +45,9 @@
@VisibleForTesting
final SizeSpecSource mSizeSpecSourceImpl;
- /** The preferred minimum (and default) size specified by apps. */
+ /** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
+ private int mOverridableMinSize;
/** Used to store values obtained from resource files. */
private Point mScreenEdgeInsets;
@@ -386,6 +387,8 @@
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task);
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets);
@@ -443,13 +446,19 @@
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
+ if (mOverrideMinSize != null
+ && (mOverrideMinSize.getWidth() < mOverridableMinSize
+ || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
+ return new Size(mOverridableMinSize, mOverridableMinSize);
+ }
+
return mOverrideMinSize;
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
if (mOverrideMinSize == null) return 0;
- return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
+ return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
}
public int getMinEdgeSize() {
@@ -505,7 +514,7 @@
if (mOverrideMinSize == null) {
return null;
}
- final Size size = mOverrideMinSize;
+ final Size size = getOverrideMinSize();
final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
if (sizeAspectRatio > aspectRatio) {
// Size is wider, fix the width and increase the height
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 476a7ec..2981f5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -65,6 +66,9 @@
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 907977c..3734487 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -29,7 +29,8 @@
*/
public interface WindowDecorViewModel {
/**
- * Sets the transition starter that starts freeform task transitions.
+ * Sets the transition starter that starts freeform task transitions. Only called when
+ * {@link com.android.wm.shell.transition.Transitions#ENABLE_SHELL_TRANSITIONS} is {@code true}.
*
* @param transitionStarter the transition starter that starts freeform task transitions
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index b3a2ad3..7358da3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -45,7 +45,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
@Before
open fun before() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
similarity index 88%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
index 69eb06f..1a0fbe4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
index 191f4fa..cf696c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
@@ -30,7 +30,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) {
+class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
deleted file mode 100644
index 315e021..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +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.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DismissBubbleScreenCfArm(flicker: FlickerTest) : DismissBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 7a86c25..9367a8a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -44,7 +44,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
similarity index 89%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
index 69eb06f..85a534c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
+ DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
deleted file mode 100644
index 26e6273..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +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.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class ExpandBubbleScreenCfArm(flicker: FlickerTest) : ExpandBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
deleted file mode 100644
index ec6a9c1..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +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.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreenCfArm(flicker: FlickerTest) : LaunchBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
index 379d5e9..0b1382b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
@@ -46,7 +46,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleOnLockreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 0cda626..50507bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -42,7 +42,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
index 69eb06f..94147e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
@@ -23,4 +23,4 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 5c0f854..4be4dcd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -41,7 +41,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
similarity index 89%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
index 69eb06f..7efbcdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
+ SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 7aa40e7..82617dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -82,13 +82,33 @@
@Presubmit
@Test
- override fun pipAppLayerAlwaysVisible() {
- if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
- else {
- // pip layer in gesture nav will disappear during transition
- flicker.assertLayers {
- this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
- }
+ override fun pipAppLayerOrOverlayAlwaysVisible() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppLayerOrOverlayAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppWindowVisibleChanges() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertWm {
+ this.isAppWindowVisible(pipApp)
+ .then()
+ .isAppWindowInvisible(pipApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(pipApp, isOptional = true)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppLayerVisibleChanges() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayers {
+ this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
}
}
@@ -116,12 +136,19 @@
@Presubmit
@Test
- override fun pipLayerRemainInsideVisibleBounds() {
- if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
- else {
- // pip layer in gesture nav will disappear during transition
- flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- }
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerOrOverlayRemainInsideVisibleBounds()
+ }
+
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 5d7003f..33a6405 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -74,8 +74,14 @@
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
- open fun pipAppLayerAlwaysVisible() {
- flicker.assertLayers { this.isVisible(pipApp) }
+ open fun pipAppLayerOrOverlayAlwaysVisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ .then()
+ .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ .then()
+ .isVisible(pipApp)
+ }
}
/**
@@ -94,8 +100,10 @@
*/
@Presubmit
@Test
- open fun pipLayerRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) ) {
+ coversAtMost(displayBounds)
+ }
}
/** Checks that the visible region of [pipApp] always reduces during the animation */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 02d50f4..afc4106 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
@@ -197,7 +198,7 @@
}
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 267424412)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 7466916..691b087 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -19,6 +19,7 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for pip expand tests */
@@ -32,7 +33,9 @@
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
+ coversAtMost(displayBounds)
+ }
}
/**
@@ -42,7 +45,9 @@
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
+ coversAtMost(displayBounds)
+ }
}
/**
@@ -72,7 +77,10 @@
@Test
open fun showBothAppLayersThenHidePip() {
flicker.assertLayers {
- isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ .then()
+ .isInvisible(testApp).isVisible(pipApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 54ad991..1b48965 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -76,7 +76,9 @@
?.height
?: error("Couldn't find Nav or Task bar layer")
// The dismiss button doesn't appear at the complete bottom of the screen,
- val displayY = device.displayHeight - barLayerHeight
+ // it appears above the hot seat but `hotseatBarSize` is not available outside
+ // the platform
+ val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
// Wait until the other app is no longer visible
wmHelper
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 3d8cd2e..ec264a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -300,9 +300,9 @@
(MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
};
final Size[] minimalSizes = new Size[] {
- new Size((int) (100 * aspectRatios[0]), 100),
- new Size((int) (100 * aspectRatios[1]), 100),
- new Size((int) (100 * aspectRatios[2]), 100)
+ new Size((int) (200 * aspectRatios[0]), 200),
+ new Size((int) (200 * aspectRatios[1]), 200),
+ new Size((int) (200 * aspectRatios[2]), 200)
};
for (int i = 0; i < aspectRatios.length; i++) {
final float aspectRatio = aspectRatios[i];
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index ede5bde..341a451 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -162,10 +162,10 @@
@Test
public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
final Runnable callback = mock(Runnable.class);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
verify(callback, never()).run();
}
@@ -175,11 +175,11 @@
mPipBoundsState.setOverrideMinSize(null);
assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(5, 10));
- assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(100, 110));
+ assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(15, 10));
- assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(150, 200));
+ assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
}
@Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index dd6c2bc..eeed226 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -553,6 +553,7 @@
"RootRenderNode.cpp",
"SkiaCanvas.cpp",
"SkiaInterpolator.cpp",
+ "Tonemapper.cpp",
"VectorDrawable.cpp",
],
@@ -611,7 +612,6 @@
"ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
- "Tonemapper.cpp",
"TreeInfo.cpp",
"WebViewFunctorManager.cpp",
"protos/graphicsstats.proto",
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index c46a2d3..291f4cf 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -45,4 +45,4 @@
SkColor transformColor(ColorTransform transform, SkColor color);
SkColor transformColorInverse(ColorTransform transform, SkColor color);
-} // namespace android::uirenderer;
\ No newline at end of file
+} // namespace android::uirenderer
diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h
index e45db01..959cf74 100644
--- a/libs/hwui/ColorMode.h
+++ b/libs/hwui/ColorMode.h
@@ -27,6 +27,9 @@
WideColorGamut = 1,
// Extended range Display P3
Hdr = 2,
+ // Extended range Display P3 10-bit
+ // for test purposes only, not shippable due to insuffient alpha
+ Hdr10 = 3,
// Alpha 8
A8 = 4,
};
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3f7c4f0..430e69e 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -43,6 +43,7 @@
#include "SkRegion.h"
#include "SkTextBlob.h"
#include "SkVertices.h"
+#include "Tonemapper.h"
#include "VectorDrawable.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "include/gpu/GrDirectContext.h"
@@ -344,7 +345,9 @@
SkPaint paint;
BitmapPalette palette;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImage(image.get(), x, y, sampling, &paint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImage(image.get(), x, y, sampling, &newPaint);
}
};
struct DrawImageRect final : Op {
@@ -366,7 +369,9 @@
SkCanvas::SrcRectConstraint constraint;
BitmapPalette palette;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint);
}
};
struct DrawImageLattice final : Op {
@@ -399,8 +404,10 @@
auto flags =
(0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>(
this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor));
- c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst,
- filter, &paint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter,
+ &newPaint);
}
};
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index a7e76b6..0d39f0e 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -18,7 +18,10 @@
#include <SkRuntimeEffect.h>
#include <log/log.h>
+// libshaders only exists on Android devices
+#ifdef __ANDROID__
#include <shaders/shaders.h>
+#endif
#include "utils/Color.h"
@@ -26,6 +29,8 @@
namespace {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
public:
explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
@@ -59,20 +64,21 @@
return effectBuilder.makeColorFilter();
}
-static bool extractTransfer(ui::Dataspace dataspace) {
- return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
+ return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
}
static bool isHdrDataspace(ui::Dataspace dataspace) {
const auto transfer = extractTransfer(dataspace);
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+ return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
}
static ui::Dataspace getDataspace(const SkImageInfo& image) {
return static_cast<ui::Dataspace>(
ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
}
+#endif
} // namespace
@@ -80,6 +86,8 @@
// shader and tag it on the supplied paint.
void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
SkPaint& paint) {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
const auto sourceDataspace = getDataspace(source);
const auto destinationDataspace = getDataspace(destination);
@@ -102,6 +110,9 @@
paint.setColorFilter(colorFilter);
}
}
+#else
+ return;
+#endif
}
} // namespace android::uirenderer
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 2017eb6..6628463 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -646,6 +646,11 @@
mSurfaceColorSpace = SkColorSpace::MakeRGB(
GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
break;
+ case ColorMode::Hdr10:
+ mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ break;
case ColorMode::A8:
mSurfaceColorType = SkColorType::kAlpha_8_SkColorType;
mSurfaceColorSpace = nullptr;
@@ -654,7 +659,7 @@
}
void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
- if (mColorMode == ColorMode::Hdr) {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
SkNamedGamut::kDisplayP3);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8877678..f10b2b2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -301,7 +301,8 @@
float CanvasContext::setColorMode(ColorMode mode) {
if (mode != mColorMode) {
- if (mode == ColorMode::Hdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
+ const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
+ if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
mode = ColorMode::WideColorGamut;
}
mColorMode = mode;
@@ -311,13 +312,15 @@
switch (mColorMode) {
case ColorMode::Hdr:
return 3.f; // TODO: Refine this number
+ case ColorMode::Hdr10:
+ return 10.f;
default:
return 1.f;
}
}
float CanvasContext::targetSdrHdrRatio() const {
- if (mColorMode == ColorMode::Hdr) {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
return mTargetSdrHdrRatio;
} else {
return 1.f;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 5b7cf75..4fb114b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -455,6 +455,7 @@
// composer3 support, just treat HDR as equivalent to wide color gamut if
// the GLES path is still being hit
case ColorMode::Hdr:
+ case ColorMode::Hdr10:
case ColorMode::WideColorGamut: {
skcms_Matrix3x3 colorGamut;
LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index bd85b7769..31b4b20 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -150,7 +150,7 @@
float RenderProxy::setColorMode(ColorMode mode) {
// We only need to figure out what the renderer supports for HDR, otherwise this can stay
// an async call since we already know the return value
- if (mode == ColorMode::Hdr) {
+ if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
return mRenderThread.queue().runSync(
[=]() -> float { return mContext->setColorMode(mode); });
} else {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 2efa5d6..21b6c44 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -201,7 +201,7 @@
outWindowInfo->colorspace = colorSpace;
outWindowInfo->colorMode = colorMode;
- if (colorMode == ColorMode::Hdr) {
+ if (colorMode == ColorMode::Hdr || colorMode == ColorMode::Hdr10) {
outWindowInfo->dataspace =
static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
} else {
@@ -509,7 +509,7 @@
mNativeBuffers[i].skSurface.reset();
}
- if (mWindowInfo.colorMode == ColorMode::Hdr) {
+ if (mWindowInfo.colorMode == ColorMode::Hdr || mWindowInfo.colorMode == ColorMode::Hdr10) {
mWindowInfo.dataspace =
static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
} else {
@@ -521,7 +521,7 @@
"Unsupported colorspace");
if (mNativeWindow) {
- int err = native_window_set_buffers_data_space(mNativeWindow.get(), mWindowInfo.dataspace);
+ int err = ANativeWindow_setBuffersDataSpace(mNativeWindow.get(), mWindowInfo.dataspace);
if (err != 0) {
ALOGE("VulkanSurface::setColorSpace() native_window_set_buffers_data_space(%d) "
"failed: %s (%d)",
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index 96117ef..a26249a 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -281,7 +281,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
- prefix = {"DISABLE_REASON_"},
+ prefix = {"SUBTEXT_"},
value = {
SUBTEXT_NONE,
SUBTEXT_SUBSCRIPTION_REQUIRED,
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 360073d..cdaa3e5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -606,22 +606,22 @@
}
/**
- * Receives requested recording info.
+ * Receives the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfo The requested recording info. Null if recording not found.
- * @hide
+ * @see #requestTvRecordingInfo(String)
+ * @param recordingInfo The requested recording info. {@code null} if no recording found.
*/
public void onTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
}
/**
- * Receives requested recording info.
+ * Receives requested recording info list.
*
- * @param recordingInfoList The requested recording info list. Null if recording not found.
- * @hide
+ * @see #requestTvRecordingInfoList(int)
+ * @param recordingInfoList The list of recording info requested. Returns an empty list if
+ * no matching recording info found.
*/
- public void onTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
- }
+ public void onTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {}
/**
* Receives started recording's ID.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 78c6bcf..0b44a89 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -635,8 +635,8 @@
/**
* Sends the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfo The recording info requested {@code null} if no recording found.
- * @hide
+ * @see TvInteractiveAppService.Session#requestTvRecordingInfo(String)
+ * @param recordingInfo The recording info requested. {@code null} if no recording found.
*/
public void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
if (DEBUG) {
@@ -650,10 +650,11 @@
/**
* Sends the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfoList The list of recording info requested.
- * @hide
+ * @see TvInteractiveAppService.Session#requestTvRecordingInfoList(int)
+ * @param recordingInfoList The list of recording info requested. Returns an empty list if no
+ * matching recording info found.
*/
- public void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
+ public void sendTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {
if (DEBUG) {
Log.d(TAG, "sendTvRecordingInfoList");
}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index d958db1..8f2e7b5 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -18,10 +18,15 @@
import android.annotation.BytesLong;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.media.AudioPresentation;
import android.media.MediaCodec.LinearBlock;
+import java.util.Collections;
+import java.util.List;
+
/**
* Filter event sent from {@link Filter} objects with media type.
*
@@ -51,12 +56,13 @@
private final boolean mIsPrivateData;
private final int mScIndexMask;
private final AudioDescriptor mExtraMetaData;
+ private final List<AudioPresentation> mAudioPresentations;
// This constructor is used by JNI code only
private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
int mpuSequenceNumber, boolean isPrivateData, int scIndexMask,
- AudioDescriptor extraMetaData) {
+ AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations) {
mStreamId = streamId;
mIsPtsPresent = isPtsPresent;
mPts = pts;
@@ -71,6 +77,7 @@
mIsPrivateData = isPrivateData;
mScIndexMask = scIndexMask;
mExtraMetaData = extraMetaData;
+ mAudioPresentations = audioPresentations;
}
/**
@@ -215,6 +222,13 @@
return mExtraMetaData;
}
+ /**
+ * Gets audio presentations.
+ */
+ @NonNull
+ public List<AudioPresentation> getAudioPresentations() {
+ return mAudioPresentations == null ? Collections.emptyList() : mAudioPresentations;
+ }
/**
* Finalize the MediaEvent object.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index a0304bb..ed1072c 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -88,6 +88,7 @@
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
"android.hardware.drm-V1-ndk",
+ "android.hardware.tv.tuner-V2-ndk",
],
header_libs: [
diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h
index a3adddd..0c83ddf 100644
--- a/media/jni/android_media_AudioPresentation.h
+++ b/media/jni/android_media_AudioPresentation.h
@@ -19,10 +19,14 @@
#include "jni.h"
+#include <aidl/android/hardware/tv/tuner/AudioPresentation.h>
#include <media/stagefright/foundation/ADebug.h> // CHECK
#include <media/stagefright/foundation/AudioPresentationInfo.h>
#include <nativehelper/ScopedLocalRef.h>
+using ::aidl::android::hardware::tv::tuner::AudioPreselectionRenderingIndicationType;
+using TunerAudioPresentation = ::aidl::android::hardware::tv::tuner::AudioPresentation;
+
namespace android {
struct JAudioPresentationInfo {
@@ -97,6 +101,38 @@
}
static void addPresentations(JNIEnv *env, const fields_t& fields,
+ const std::vector<TunerAudioPresentation>& tunerAudioPresentations,
+ jobject presentationsJObj) {
+ AudioPresentationCollection apc = {};
+ static const std::map<AudioPreselectionRenderingIndicationType, MasteringIndication> mMap {
+ { AudioPreselectionRenderingIndicationType::NOT_INDICATED, MASTERING_NOT_INDICATED },
+ { AudioPreselectionRenderingIndicationType::STEREO, MASTERED_FOR_STEREO },
+ { AudioPreselectionRenderingIndicationType::TWO_DIMENSIONAL, MASTERED_FOR_SURROUND },
+ { AudioPreselectionRenderingIndicationType::THREE_DIMENSIONAL, MASTERED_FOR_3D },
+ { AudioPreselectionRenderingIndicationType::HEADPHONE, MASTERED_FOR_HEADPHONE },
+ };
+ for (const auto& tap : tunerAudioPresentations) {
+ AudioPresentationV1 ap;
+ ap.mPresentationId = tap.preselection.preselectionId;
+ ap.mProgramId = tap.ac4ShortProgramId;
+ for (const auto& md : tap.preselection.labels) {
+ ap.mLabels.insert(std::pair(md.language, md.text));
+ }
+ ap.mLanguage = tap.preselection.language;
+ ap.mMasteringIndication = MASTERING_NOT_INDICATED;
+ auto masteringSearch = mMap.find(tap.preselection.renderingIndication);
+ if (masteringSearch != mMap.end()) {
+ ap.mMasteringIndication = masteringSearch->second;
+ }
+ ap.mAudioDescriptionAvailable = tap.preselection.hasAudioDescription;
+ ap.mSpokenSubtitlesAvailable = tap.preselection.hasSpokenSubtitles;
+ ap.mDialogueEnhancementAvailable = tap.preselection.hasDialogueEnhancement;
+ apc.push_back(ap);
+ }
+ addPresentations(env, fields, apc, presentationsJObj);
+ }
+
+ static void addPresentations(JNIEnv *env, const fields_t& fields,
const AudioPresentationCollection& presentations, jobject presentationsJObj) {
for (const auto& ap : presentations) {
ScopedLocalRef<jobject> jLabelObject = convertLabelsToMap(env, fields, ap.mLabels);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 625e842..f56e236 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -20,6 +20,7 @@
#include "android_media_tv_Tuner.h"
#include <aidl/android/hardware/tv/tuner/AudioExtraMetaData.h>
+#include <aidl/android/hardware/tv/tuner/AudioPresentation.h>
#include <aidl/android/hardware/tv/tuner/AudioStreamType.h>
#include <aidl/android/hardware/tv/tuner/AvStreamType.h>
#include <aidl/android/hardware/tv/tuner/Constant.h>
@@ -161,12 +162,14 @@
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
+#include "android_media_AudioPresentation.h"
#include "android_media_MediaCodecLinearBlock.h"
#include "android_runtime/AndroidRuntime.h"
#pragma GCC diagnostic ignored "-Wunused-function"
using ::aidl::android::hardware::tv::tuner::AudioExtraMetaData;
+using ::aidl::android::hardware::tv::tuner::AudioPreselection;
using ::aidl::android::hardware::tv::tuner::AudioStreamType;
using ::aidl::android::hardware::tv::tuner::AvStreamType;
using ::aidl::android::hardware::tv::tuner::Constant;
@@ -362,6 +365,9 @@
}
namespace android {
+
+static JAudioPresentationInfo::fields_t gAudioPresentationFields;
+
/////////////// LnbClientCallbackImpl ///////////////////////
void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) {
ALOGV("LnbClientCallbackImpl::onEvent, type=%d", lnbEventType);
@@ -634,27 +640,45 @@
eventClazz,
"<init>",
"(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
- "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;)V");
+ "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
+ "Ljava/util/List;)V");
jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
jobject audioDescriptor = nullptr;
- if (mediaEvent.extraMetaData.getTag() == DemuxFilterMediaEventExtraMetaData::Tag::audio) {
- jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
- jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
+ gAudioPresentationFields.init(env);
+ jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
+ switch (mediaEvent.extraMetaData.getTag()) {
+ case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
+ jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
+ jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
- const AudioExtraMetaData &ad =
- mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
- jbyte adFade = ad.adFade;
- jbyte adPan = ad.adPan;
- jchar versionTextTag = ad.versionTextTag;
- jbyte adGainCenter = ad.adGainCenter;
- jbyte adGainFront = ad.adGainFront;
- jbyte adGainSurround = ad.adGainSurround;
+ const AudioExtraMetaData &ad =
+ mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
+ jbyte adFade = ad.adFade;
+ jbyte adPan = ad.adPan;
+ jchar versionTextTag = ad.versionTextTag;
+ jbyte adGainCenter = ad.adGainCenter;
+ jbyte adGainFront = ad.adGainFront;
+ jbyte adGainSurround = ad.adGainSurround;
- audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
- adGainCenter, adGainFront, adGainSurround);
- env->DeleteLocalRef(adClazz);
+ audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
+ adGainCenter, adGainFront, adGainSurround);
+ env->DeleteLocalRef(adClazz);
+ break;
+ }
+ case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
+ JAudioPresentationInfo::addPresentations(
+ env, gAudioPresentationFields,
+ mediaEvent.extraMetaData
+ .get<DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations>(),
+ presentationsJObj);
+ break;
+ }
+ default: {
+ ALOGE("FilterClientCallbackImpl::getMediaEvent: unknown extraMetaData");
+ break;
+ }
}
jlong dataLength = mediaEvent.dataLength;
@@ -683,7 +707,8 @@
jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
- mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor);
+ mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor,
+ presentationsJObj);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -702,6 +727,7 @@
}
env->DeleteLocalRef(obj);
env->DeleteLocalRef(eventClazz);
+ env->DeleteLocalRef(presentationsJObj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
old mode 100755
new mode 100644
index 93387e2..c6217ec
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -141,7 +141,7 @@
File file = new File(mPackageURI.getPath());
try {
final InstallInfo result = getPackageManager().getPackageInstaller()
- .getInstallInfo(file, 0);
+ .readInstallInfo(file, 0);
params.setAppPackageName(result.getPackageName());
params.setInstallLocation(result.getInstallLocation());
try {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 49c9188..3ec81aa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -392,7 +392,7 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- if (info == null || !info.getIsPreApprovalRequested()) {
+ if (info == null || !info.isPreApprovalRequested()) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml b/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml
new file mode 100644
index 0000000..141886c
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<resources>
+ <declare-styleable name="IllustrationPreference">
+ <attr name="dynamicColor" format="boolean" />
+ </declare-styleable>
+</resources>
\ 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 1592094..3b90275 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -61,6 +61,8 @@
private View mMiddleGroundView;
private OnBindListener mOnBindListener;
+ private boolean mLottieDynamicColor;
+
/**
* Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
*/
@@ -146,6 +148,10 @@
ColorUtils.applyDynamicColors(getContext(), illustrationView);
}
+ if (mLottieDynamicColor) {
+ LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
+ }
+
if (mOnBindListener != null) {
mOnBindListener.onBind(illustrationView);
}
@@ -262,6 +268,21 @@
}
}
+ /**
+ * Sets the lottie illustration apply dynamic color.
+ */
+ public void applyDynamicColor() {
+ mLottieDynamicColor = true;
+ notifyChanged();
+ }
+
+ /**
+ * Return if the lottie illustration apply dynamic color or not.
+ */
+ public boolean isApplyDynamicColor() {
+ return mLottieDynamicColor;
+ }
+
private void resetImageResourceCache() {
mImageDrawable = null;
mImageUri = null;
@@ -403,9 +424,15 @@
mIsAutoScale = false;
if (attrs != null) {
- final TypedArray a = context.obtainStyledAttributes(attrs,
+ TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
mImageResId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0);
+
+ a = context.obtainStyledAttributes(attrs,
+ R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
+ mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor,
+ false);
+
a.recycle();
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index cb58abf6..7a1fad0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -18,16 +18,17 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.CategoryTitle
@@ -36,50 +37,60 @@
object CategoryPageProvider : SettingsPageProvider {
override val name = "Category"
+ private val owner = createSettingsPage()
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
override val onClick = navigator(name)
})
}
+ .setSearchDataFn { EntrySearchData(title = TITLE) }
}
override fun getTitle(arguments: Bundle?): String {
return TITLE
}
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 1", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 1", summary = "Summary 1") }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 2", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 2") }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 3", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
+ .build()
+
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 4", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 4", summary = "Summary 4") }
+ .build()
+ )
+ return entryList
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
+ val entries = buildEntry(arguments)
RegularScaffold(title = getTitle(arguments)) {
CategoryTitle("Category A")
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 1"
- override val summary = stateOf("Summary 1")
- }
- })
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 2"
- override val summary = stateOf("Summary 2")
- }
- })
+ entries[0].UiLayout()
+ entries[1].UiLayout()
+
Category("Category B") {
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 3"
- override val summary = stateOf("Summary 3")
- }
- })
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 4"
- override val summary = stateOf("Summary 4")
- }
- })
+ entries[2].UiLayout()
+ entries[3].UiLayout()
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
index d40f322..3cae39f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -1,2 +1,5 @@
# Default reviewers for this and subdirectories.
shaoweishen@google.com
+
+#Android Media - For minor changes and renames only.
+aquilescanta@google.com #{LAST_RESORT_SUGGESTION}
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 103512d..21e119a 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
@@ -215,4 +215,20 @@
assertThat(mOnBindListenerAnimationView).isNull();
}
+
+ @Test
+ public void onBindViewHolder_default_shouldNotApplyDynamicColor() {
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mPreference.isApplyDynamicColor()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_applyDynamicColor_shouldReturnTrue() {
+ mPreference.applyDynamicColor();
+
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mPreference.isApplyDynamicColor()).isTrue();
+ }
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index cb37c07..a719d77 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -677,7 +677,7 @@
if (mScreenshotFd != null) {
writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT);
}
- mIDumpstateListener.onFinished();
+ mIDumpstateListener.onFinished("");
getInstrumentation().waitForIdleSync();
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 0b4d7cd..77412d9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -19,11 +19,12 @@
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
- <application>
+ <application android:supportsRtl="true">
<service
android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
android:exported="false"
- android:label="Accessibility Menu (System)"
+ android:label="@string/accessibility_menu_service_name"
+ android:icon="@drawable/ic_logo_app"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
new file mode 100644
index 0000000..7a4fbb2
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
@@ -0,0 +1,276 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="412dp"
+ android:height="300dp"
+ android:viewportWidth="412"
+ android:viewportHeight="300">
+ <group>
+ <clip-path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
+ <path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M46.23,52.14h160v200h-160z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
+ android:fillColor="#3B4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
+ android:fillColor="#327969"/>
+ <group>
+ <clip-path
+ android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
+ android:fillColor="#327969"/>
+ <path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillColor="#8B3737"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
+ android:fillColor="#8B3737"/>
+ <group>
+ <clip-path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
+ <clip-path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
+ android:fillColor="#9F3EBF"/>
+ <path
+ android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
+ android:fillColor="#521BBF"/>
+ <path
+ android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
+ android:fillColor="#438947"/>
+ <path
+ android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#7F868C"/>
+ <path
+ android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
+ android:fillColor="#7F868C"/>
+ <path
+ android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
+ android:fillColor="#7F868C"/>
+ <group>
+ <clip-path
+ android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"/>
+ <path
+ android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M46,52.27h160v200h-160z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M85.98,240.17L86.68,239.47L83.98,236.77H89.88V235.77H83.98L86.68,233.07L85.98,232.37L82.08,236.27L85.98,240.17Z"
+ android:fillColor="#3B4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.48,220.27V252.27H127.48V220.27H125.48Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M165.98,232.37L165.28,233.07L167.98,235.77H162.08V236.77H167.98L165.28,239.47L165.98,240.17L169.88,236.27L165.98,232.37Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.48,221.27H206.48V219.27H46.48V221.27Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M78.48,120.64L78.48,120.64A16,16 0,0 1,94.48 136.64L94.48,136.64A16,16 0,0 1,78.48 152.64L78.48,152.64A16,16 0,0 1,62.48 136.64L62.48,136.64A16,16 0,0 1,78.48 120.64z"
+ android:fillColor="#7AE2D4"/>
+ <group>
+ <clip-path
+ android:pathData="M73.13,139.09V134.29H76.33L80.33,130.29V142.99L76.33,138.99H73.13V139.09ZM81.83,133.49C83.03,134.09 83.83,135.29 83.83,136.69C83.83,138.09 83.03,139.29 81.83,139.89V133.49Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.13,127.19H69.13V146.19H88.13V127.19Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.48,172.64L78.48,172.64A16,16 0,0 1,94.48 188.64L94.48,188.64A16,16 0,0 1,78.48 204.64L78.48,204.64A16,16 0,0 1,62.48 188.64L62.48,188.64A16,16 0,0 1,78.48 172.64z"
+ android:fillColor="#FDD663"/>
+ <path
+ android:pathData="M87.43,188.64L84.83,191.24V194.94H81.13L78.53,197.54L75.93,194.94H72.13V191.24L69.53,188.64L72.13,186.04V182.34H75.83L78.43,179.74L81.03,182.34H84.73V186.04L87.43,188.64ZM73.73,188.64C73.73,191.24 75.83,193.44 78.53,193.44C81.13,193.44 83.33,191.34 83.33,188.64C83.33,185.94 81.23,183.84 78.53,183.84C75.83,183.84 73.73,185.94 73.73,188.64Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.43,188.04H76.43V189.54H80.43V188.04Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.48,120.64L126.48,120.64A16,16 0,0 1,142.48 136.64L142.48,136.64A16,16 0,0 1,126.48 152.64L126.48,152.64A16,16 0,0 1,110.48 136.64L110.48,136.64A16,16 0,0 1,126.48 120.64z"
+ android:fillColor="#7AE2D4"/>
+ <path
+ android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"
+ android:fillColor="#000000"/>
+ <group>
+ <clip-path
+ android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"/>
+ <clip-path
+ android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.08,131.24H78.08V168.28L113.52,179.07L128.08,131.24ZM128.08,129.64L138.76,80.79L78.08,67.52V129.64H128.08ZM128.08,143.64H78.08V207.68L140.21,192.14L128.08,143.64ZM128.08,142.04L113.52,94.2L78.08,104.99V142.04H128.08ZM128.08,133.34L150.44,88.62L78.08,52.44V133.34H128.08ZM128.08,139.74H78.08V223.96L152.02,183.63L128.08,139.74ZM126.49,130.27H176.49V10.92L91.43,94.63L126.49,130.27ZM126.49,142.93L91.14,178.28L176.49,263.64V142.93H126.49ZM122.49,138.92L157.84,103.56L143.2,88.92H122.49V138.92ZM119.27,138.92H69.27V188.92H119.27V138.92ZM119.27,134.21V84.22H69.27V134.21H119.27ZM122.49,134.21V184.21H142.96L157.55,169.85L122.49,134.21ZM178.08,131.24V129.64H78.08V131.24H178.08ZM117.39,178.48C97.8,174.2 83.68,156.9 83.68,136.64H183.68C183.68,109.57 164.76,86.48 138.76,80.79L117.39,178.48ZM83.68,136.64C83.68,115.28 98.7,99.44 115.95,95.13L140.21,192.14C163.86,186.23 183.68,164.79 183.68,136.64H83.68ZM178.08,143.64V142.04H78.08V143.64H178.08ZM142.64,189.87C164.49,183.22 182.08,162.77 182.08,136.74H82.08C82.08,115.7 96.27,99.45 113.52,94.2L142.64,189.87ZM182.08,136.74C182.08,112.78 166.51,90.67 142.64,83.4L113.52,179.07C94.25,173.2 82.08,155.69 82.08,136.74H182.08ZM180.08,136.54C180.08,115.18 167.65,97.22 150.44,88.62L105.72,178.06C90.91,170.65 80.08,155.1 80.08,136.54H180.08ZM78.08,133.34V139.74H178.08V133.34H78.08ZM152.02,183.63C165.86,176.08 180.08,159.73 180.08,136.54H80.08C80.08,116.14 92.5,102.19 104.14,95.84L152.02,183.63ZM76.49,130.27V142.93H176.49V130.27H76.49ZM161.85,107.57L157.84,103.56L87.13,174.27L91.14,178.28L161.85,107.57ZM122.49,88.92H119.27V188.92H122.49V88.92ZM169.27,138.92V134.21H69.27V138.92H169.27ZM119.27,184.21H122.49V84.22H119.27V184.21ZM157.55,169.85L161.56,165.91L91.43,94.63L87.42,98.58L157.55,169.85Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.48,68.64L78.48,68.64A16,16 0,0 1,94.48 84.64L94.48,84.64A16,16 0,0 1,78.48 100.64L78.48,100.64A16,16 0,0 1,62.48 84.64L62.48,84.64A16,16 0,0 1,78.48 68.64z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M86.03,82.29C86.69,82.29 87.23,81.75 87.23,81.09C87.23,80.42 86.69,79.89 86.03,79.89C85.37,79.89 84.83,80.42 84.83,81.09C84.83,81.75 85.37,82.29 86.03,82.29Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.43,86.29C83.76,86.29 84.83,85.21 84.83,83.89C84.83,82.56 83.76,81.49 82.43,81.49C81.11,81.49 80.03,82.56 80.03,83.89C80.03,85.21 81.11,86.29 82.43,86.29Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.43,92.59C83.98,92.59 85.23,91.33 85.23,89.79C85.23,88.24 83.98,86.99 82.43,86.99C80.88,86.99 79.63,88.24 79.63,89.79C79.63,91.33 80.88,92.59 82.43,92.59Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.53,86.29C77.18,86.29 79.33,84.14 79.33,81.49C79.33,78.84 77.18,76.69 74.53,76.69C71.88,76.69 69.73,78.84 69.73,81.49C69.73,84.14 71.88,86.29 74.53,86.29Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.48,120.64L174.48,120.64A16,16 0,0 1,190.48 136.64L190.48,136.64A16,16 0,0 1,174.48 152.64L174.48,152.64A16,16 0,0 1,158.48 136.64L158.48,136.64A16,16 0,0 1,174.48 120.64z"
+ android:fillColor="#EFA5DE"/>
+ <path
+ android:pathData="M178.3,130.64H170.8V142.34H178.3V130.64Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M170,132.94H167.3V140.14H170V132.94Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M182,132.94H179.3V140.14H182V132.94Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.48,172.64L126.48,172.64A16,16 0,0 1,142.48 188.64L142.48,188.64A16,16 0,0 1,126.48 204.64L126.48,204.64A16,16 0,0 1,110.48 188.64L110.48,188.64A16,16 0,0 1,126.48 172.64z"
+ android:fillColor="#FDD663"/>
+ <path
+ android:pathData="M135.48,188.64L132.88,191.24V194.94H129.08L126.48,197.54L123.88,194.94H120.08V191.24L117.48,188.64L120.08,186.04V182.34H123.78L126.38,179.74L128.98,182.34H132.68V186.04L135.48,188.64ZM121.68,188.64C121.68,191.24 123.78,193.44 126.48,193.44C129.08,193.44 131.28,191.34 131.28,188.64C131.28,185.94 129.18,183.84 126.48,183.84C123.88,183.84 121.68,185.94 121.68,188.64Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.98,188.04V186.54H125.98V188.04H124.48V189.04H125.98V190.54H126.98V189.04H128.48V188.04H126.98Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.48,68.64L126.48,68.64A16,16 0,0 1,142.48 84.64L142.48,84.64A16,16 0,0 1,126.48 100.64L126.48,100.64A16,16 0,0 1,110.48 84.64L110.48,84.64A16,16 0,0 1,126.48 68.64z"
+ android:fillColor="#D9AFFD"/>
+ <path
+ android:pathData="M128.13,78.64C128.13,79.44 127.43,80.14 126.53,80.14C125.63,80.14 124.93,79.44 124.93,78.64C124.93,77.84 125.63,77.14 126.53,77.14C127.43,77.04 128.13,77.74 128.13,78.64ZM126.53,80.84C128.73,80.84 131.23,80.64 133.23,80.04L133.63,81.54C132.13,81.94 130.43,82.14 128.83,82.34V92.14H127.23V87.64H125.63V92.14H124.13V82.34C122.53,82.24 120.83,81.94 119.33,81.54L119.73,80.04C121.83,80.64 124.23,80.84 126.53,80.84Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.48,172.64L174.48,172.64A16,16 0,0 1,190.48 188.64L190.48,188.64A16,16 0,0 1,174.48 204.64L174.48,204.64A16,16 0,0 1,158.48 188.64L158.48,188.64A16,16 0,0 1,174.48 172.64z"
+ android:fillColor="#84E39F"/>
+ <path
+ android:pathData="M178.88,186.09H178.18V184.59C178.18,182.59 176.58,180.89 174.48,180.89C172.38,180.89 170.78,182.49 170.78,184.59V186.09H170.08C169.28,186.09 168.58,186.79 168.58,187.59V194.89C168.58,195.69 169.28,196.39 170.08,196.39H178.88C179.68,196.39 180.38,195.69 180.38,194.89V187.59C180.38,186.79 179.68,186.09 178.88,186.09ZM174.48,192.69C173.68,192.69 172.98,191.99 172.98,191.19C172.98,190.39 173.68,189.69 174.48,189.69C175.28,189.69 175.98,190.39 175.98,191.19C175.98,192.09 175.28,192.69 174.48,192.69ZM172.18,186.09H176.68V184.59C176.68,183.29 175.68,182.29 174.38,182.29C173.08,182.29 172.08,183.29 172.08,184.59V186.09H172.18Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.48,68.64L174.48,68.64A16,16 0,0 1,190.48 84.64L190.48,84.64A16,16 0,0 1,174.48 100.64L174.48,100.64A16,16 0,0 1,158.48 84.64L158.48,84.64A16,16 0,0 1,174.48 68.64z"
+ android:fillColor="#DBDCE0"/>
+ <path
+ android:pathData="M173.68,77.49H175.28V85.39H173.68V77.49ZM177.98,80.39L179.08,79.29C180.58,80.59 181.58,82.49 181.58,84.69C181.58,88.59 178.38,91.79 174.48,91.79C170.58,91.79 167.38,88.59 167.38,84.69C167.38,82.49 168.38,80.59 169.88,79.29L170.98,80.39C169.78,81.39 168.98,82.99 168.98,84.69C168.98,87.79 171.48,90.29 174.58,90.29C177.68,90.29 180.18,87.79 180.18,84.69C180.08,82.89 179.18,81.39 177.98,80.39Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62,51.82L349.54,51.82A14.5,14.5 0,0 1,364.04 66.32L364.04,236.27A14.5,14.5 0,0 1,349.54 250.77L62,250.77A14.5,14.5 0,0 1,47.5 236.27L47.5,66.32A14.5,14.5 0,0 1,62 51.82z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#7F868C"/>
+ <path
+ android:pathData="M311.22,50.49C311.22,49.11 312.33,48 313.7,48L322.61,48C323.98,48 325.08,49.11 325.08,50.49L319.14,51.48L311.22,50.49Z"
+ android:fillColor="#7F868C"/>
+ <path
+ android:pathData="M263.36,50.49C263.36,49.11 264.47,48 265.83,48L287.62,48C288.99,48 290.1,49.11 290.1,50.49L277.22,51.48L263.36,50.49Z"
+ android:fillColor="#7F868C"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
new file mode 100644
index 0000000..f0fbc48
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
@@ -0,0 +1,141 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="412dp"
+ android:height="300dp"
+ android:viewportWidth="412"
+ android:viewportHeight="300">
+ <group>
+ <clip-path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
+ <path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M46.23,52.14h160v200h-160z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
+ android:fillColor="#E8EAED"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
+ android:fillColor="#327969"/>
+ <group>
+ <clip-path
+ android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
+ android:fillColor="#327969"/>
+ <path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
+ android:fillColor="#ffffff"/>
+ <group>
+ <clip-path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
+ <clip-path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
+ android:fillColor="#9F3EBF"/>
+ <path
+ android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
+ android:fillColor="#521BBF"/>
+ <path
+ android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
+ android:fillColor="#438947"/>
+ <path
+ android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#DADCE0"/>
+ <path
+ android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
+ android:fillColor="#DADCE0"/>
+ <path
+ android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
+ android:fillColor="#DADCE0"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
deleted file mode 100644
index 79e0e08d..0000000
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<vector android:height="48dp" android:viewportHeight="192.0"
- android:viewportWidth="192.0" android:width="48dp"
- xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#34a853" android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
- <path android:pathData="M137.61,94.07l-17,17 -17,-17 -17,17L70.3,94.72l-17,17L125.66,184h15.56a14.32,14.32 0,0 0,13.67 -10.19l15.25,-47.21Z">
- <aapt:attr name="android:fillColor">
- <gradient android:endX="27152.64"
- android:endY="32745.600000000002"
- android:startX="20910.72"
- android:startY="21934.079999999998" android:type="linear">
- <item android:color="#33263238" android:offset="0.0"/>
- <item android:color="#11205432" android:offset="0.47"/>
- <item android:color="#051E6130" android:offset="1.0"/>
- </gradient>
- </aapt:attr>
- </path>
- <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M50.14,100.11a12,12 0,1 1,11.39 15.77,11.72 11.72,0 0,1 -5,-1.1l-3.41,-3.4ZM129.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.4 91.88ZM95.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 91.88Z"/>
- <path android:fillColor="#fff"
- android:pathData="M61.53,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,61.53 90.88ZM129.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.41 90.88ZM95.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 90.88Z"
- android:strokeAlpha="0" android:strokeColor="#000"
- android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.5"/>
- <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M184,80.91a14.33,14.33 0,0 1,-0.63 4.7l-28.48,88.18A14.33,14.33 0,0 1,141.21 184H50.84a14.33,14.33 0,0 1,-13.7 -10.26l-28.53,-90A14.49,14.49 0,0 1,8 79.11a14.3,14.3 0,0 0,0.61 3.64l28.53,90A14.33,14.33 0,0 0,50.84 183h90.37a14.33,14.33 0,0 0,13.67 -10.19l28.48,-88.18A14.79,14.79 0,0 0,184 80.91Z"/>
- <path android:fillAlpha="0.2" android:fillColor="#fff" android:pathData="M184,81.89A14.46,14.46 0,0 0,178.57 71L104.76,12.1A14.21,14.21 0,0 0,87.15 12L13.58,69.12A14.5,14.5 0,0 0,8 80.09a14.5,14.5 0,0 1,5.57 -12L87.15,11a14.21,14.21 0,0 1,17.61 0.12L178.57,70A14.48,14.48 0,0 1,184 81.89Z"/>
- <path android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
- <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@color/colorAccessibilityMenuIcon" />
- <foreground>
- <inset
- android:drawable="@drawable/ic_a11y_menu_round"
- android:inset="21.88%" />
- </foreground>
- </adaptive-icon>
-</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml
new file mode 100644
index 0000000..31fe99e1f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml
@@ -0,0 +1,8 @@
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/colorAccessibilityMenuIcon" />
+ <foreground>
+ <inset
+ android:drawable="@drawable/ic_logo_app_foreground"
+ android:inset="21.88%" />
+ </foreground>
+</adaptive-icon>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app_foreground.xml
similarity index 100%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app_foreground.xml
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index ecb2bf4..ca6c332 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -228,6 +228,13 @@
mA11yMenuLayout.hideMenu();
}
+ /**
+ * Adjusts brightness using the same logic and utils class as the SystemUI brightness slider.
+ *
+ * @see BrightnessUtils
+ * @see com.android.systemui.settings.brightness.BrightnessController
+ * @param increment The increment amount in gamma-space
+ */
private void adjustBrightness(int increment) {
BrightnessInfo info = getDisplay().getBrightnessInfo();
int gamma = BrightnessUtils.convertLinearToGammaFloat(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 4322d53..c764d53 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -44,6 +44,20 @@
}
// language=AGSL
companion object {
+ // Default fade in/ out values. The value range is [0,1].
+ const val DEFAULT_FADE_IN_START = 0f
+ const val DEFAULT_FADE_OUT_END = 1f
+
+ const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f
+ const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f
+
+ const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f
+ const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f
+
+ const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f
+ const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f
+ const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f
+
private const val SHADER_UNIFORMS =
"""
uniform vec2 in_center;
@@ -73,7 +87,7 @@
* (1.-sparkleRing) * in_fadeSparkle;
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
- float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing * in_sparkle_strength;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
@@ -143,11 +157,26 @@
}
private fun subProgress(start: Float, end: Float, progress: Float): Float {
+ // Avoid division by 0.
+ if (start == end) {
+ // If start and end are the same and progress has exceeded the start/ end point,
+ // treat it as 1, otherwise 0.
+ return if (progress > start) 1f else 0f
+ }
+
val min = Math.min(start, end)
val max = Math.max(start, end)
val sub = Math.min(Math.max(progress, min), max)
return (sub - start) / (end - start)
}
+
+ private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float {
+ val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress)
+ val fadeOut =
+ 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress)
+
+ return Math.min(fadeIn, fadeOut)
+ }
}
/** Sets the center position of the ripple. */
@@ -172,17 +201,9 @@
field = value
progress = Interpolators.STANDARD.getInterpolation(value)
- val fadeIn = subProgress(0f, 0.1f, value)
- val fadeOutNoise = subProgress(0.4f, 1f, value)
- var fadeOutRipple = 0f
- var fadeFill = 0f
- if (!rippleFill) {
- fadeFill = subProgress(0f, 0.6f, value)
- fadeOutRipple = subProgress(0.3f, 1f, value)
- }
- setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
- setFloatUniform("in_fadeFill", 1 - fadeFill)
- setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
+ setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value))
+ setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value))
+ setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value))
}
/** Progress with Standard easing curve applied. */
@@ -232,21 +253,130 @@
setFloatUniform("in_distort_xy", 75 * value)
}
+ /**
+ * Pixel density of the screen that the effects are rendered to.
+ *
+ * <p>This value should come from [resources.displayMetrics.density].
+ */
var pixelDensity: Float = 1.0f
set(value) {
field = value
setFloatUniform("in_pixelDensity", value)
}
- /**
- * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
- * False for a ring effect.
- */
- var rippleFill: Boolean = false
-
var currentWidth: Float = 0f
private set
var currentHeight: Float = 0f
private set
+
+ /**
+ * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+ * False for a ring effect.
+ *
+ * <p>You must reset fade params after changing this.
+ *
+ * TODO(b/265326983): Remove this and only expose fade params.
+ */
+ var rippleFill: Boolean = false
+ set(value) {
+ if (value) {
+ baseRingFadeParams.fadeOutStart = 1f
+ baseRingFadeParams.fadeOutEnd = 1f
+
+ centerFillFadeParams.fadeInStart = 0f
+ centerFillFadeParams.fadeInEnd = 0f
+ centerFillFadeParams.fadeOutStart = 1f
+ centerFillFadeParams.fadeOutEnd = 1f
+ } else {
+ // Set back to the original fade parameters.
+ // Ideally this should be set by the client as they know the initial value.
+ baseRingFadeParams.fadeOutStart = DEFAULT_BASE_RING_FADE_OUT_START
+ baseRingFadeParams.fadeOutEnd = DEFAULT_FADE_OUT_END
+
+ centerFillFadeParams.fadeInStart = DEFAULT_FADE_IN_START
+ centerFillFadeParams.fadeInEnd = DEFAULT_CENTER_FILL_FADE_IN_END
+ centerFillFadeParams.fadeOutStart = DEFAULT_CENTER_FILL_FADE_OUT_START
+ centerFillFadeParams.fadeOutEnd = DEFAULT_CENTER_FILL_FADE_OUT_END
+ }
+ field = value
+ }
+
+ /** Parameters that are used to fade in/ out of the sparkle ring. */
+ val sparkleRingFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_SPARKLE_RING_FADE_IN_END,
+ DEFAULT_SPARKLE_RING_FADE_OUT_START,
+ DEFAULT_FADE_OUT_END
+ )
+
+ /**
+ * Parameters that are used to fade in/ out of the base ring.
+ *
+ * <p>Note that the shader draws the sparkle ring on top of the base ring.
+ */
+ val baseRingFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_BASE_RING_FADE_IN_END,
+ DEFAULT_BASE_RING_FADE_OUT_START,
+ DEFAULT_FADE_OUT_END
+ )
+
+ /**
+ * Parameters that are used to fade in/ out of the center fill.
+ *
+ * <p>Note that if [rippleFill] is set to true, those will be ignored and the center fill will
+ * be always full alpha.
+ */
+ val centerFillFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_CENTER_FILL_FADE_IN_END,
+ DEFAULT_CENTER_FILL_FADE_OUT_START,
+ DEFAULT_CENTER_FILL_FADE_OUT_END
+ )
+
+ /**
+ * Parameters used for fade in and outs of the ripple.
+ *
+ * <p>Note that all the fade in/ outs are "linear" progression.
+ * ```
+ * (opacity)
+ * 1
+ * │
+ * maxAlpha ← ――――――――――――
+ * │ / \
+ * │ / \
+ * minAlpha ←――――/ \―――― (alpha change)
+ * │
+ * │
+ * 0 ―――↑―――↑―――――――――↑―――↑――――1 (progress)
+ * fadeIn fadeOut
+ * Start & End Start & End
+ * ```
+ * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and
+ * [fadeOutEnd] to 1.
+ */
+ data class FadeParams(
+ /**
+ * The starting point of the fade out which ends at [fadeInEnd], given that the animation
+ * goes from 0 to 1.
+ */
+ var fadeInStart: Float = DEFAULT_FADE_IN_START,
+ /**
+ * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the
+ * animation goes from 0 to 1.
+ */
+ var fadeInEnd: Float,
+ /**
+ * The starting point of the fade out which ends at 1, given that the animation goes from 0
+ * to 1.
+ */
+ var fadeOutStart: Float,
+
+ /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
+ var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
+ )
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 59b4848..a523cf1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,15 +21,14 @@
import android.provider.Settings
import android.util.Log
import androidx.annotation.OpenForTesting
-import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
-import org.json.JSONObject
private val TAG = ClockRegistry::class.simpleName
private const val DEBUG = true
@@ -64,34 +63,54 @@
disconnectClocks(plugin)
}
- open var currentClockId: ClockId
+ open var settings: ClockSettings?
get() {
- return try {
+ try {
val json = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
if (json == null || json.isEmpty()) {
- return fallbackClockId
+ return null
}
- ClockSetting.deserialize(json).clockId
+ return ClockSettings.deserialize(json)
} catch (ex: Exception) {
- Log.e(TAG, "Failed to parse clock setting", ex)
- fallbackClockId
+ Log.e(TAG, "Failed to parse clock settings", ex)
+ return null
}
}
- set(value) {
+ protected set(value) {
try {
- val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
+ val json = if (value != null) {
+ value._applied_timestamp = System.currentTimeMillis()
+ ClockSettings.serialize(value)
+ } else {
+ ""
+ }
+
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
)
} catch (ex: Exception) {
- Log.e(TAG, "Failed to set clock setting", ex)
+ Log.e(TAG, "Failed to set clock settings", ex)
}
}
+ private fun mutateSetting(mutator: (ClockSettings) -> Unit) {
+ val settings = this.settings ?: ClockSettings()
+ mutator(settings)
+ this.settings = settings
+ }
+
+ var currentClockId: ClockId
+ get() = settings?.clockId ?: fallbackClockId
+ set(value) { mutateSetting { it.clockId = value } }
+
+ var seedColor: Int?
+ get() = settings?.seedColor
+ set(value) { mutateSetting { it.seedColor = value } }
+
init {
connectClocks(defaultClockProvider)
if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
@@ -194,36 +213,16 @@
return createClock(DEFAULT_CLOCK_ID)!!
}
- private fun createClock(clockId: ClockId): ClockController? =
- availableClocks[clockId]?.provider?.createClock(clockId)
+ private fun createClock(clockId: ClockId): ClockController? {
+ val settings = this.settings ?: ClockSettings()
+ if (clockId != settings.clockId) {
+ settings.clockId = clockId
+ }
+ return availableClocks[clockId]?.provider?.createClock(settings)
+ }
private data class ClockInfo(
val metadata: ClockMetadata,
val provider: ClockProvider
)
-
- @Keep
- data class ClockSetting(
- val clockId: ClockId,
- val _applied_timestamp: Long?
- ) {
- companion object {
- private val KEY_CLOCK_ID = "clockId"
- private val KEY_TIMESTAMP = "_applied_timestamp"
-
- fun serialize(setting: ClockSetting): String {
- return JSONObject()
- .put(KEY_CLOCK_ID, setting.clockId)
- .put(KEY_TIMESTAMP, setting._applied_timestamp)
- .toString()
- }
-
- fun deserialize(jsonStr: String): ClockSetting {
- val json = JSONObject(jsonStr)
- return ClockSetting(
- json.getString(KEY_CLOCK_ID),
- if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
- }
- }
- }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 7645dec..2a40f5c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -29,6 +29,7 @@
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
@@ -46,6 +47,7 @@
ctx: Context,
private val layoutInflater: LayoutInflater,
private val resources: Resources,
+ private val settings: ClockSettings?,
) : ClockController {
override val smallClock: DefaultClockFaceController
override val largeClock: LargeClockFaceController
@@ -66,12 +68,14 @@
smallClock =
DefaultClockFaceController(
layoutInflater.inflate(R.layout.clock_default_small, parent, false)
- as AnimatableClockView
+ as AnimatableClockView,
+ settings?.seedColor
)
largeClock =
LargeClockFaceController(
layoutInflater.inflate(R.layout.clock_default_large, parent, false)
- as AnimatableClockView
+ as AnimatableClockView,
+ settings?.seedColor
)
clocks = listOf(smallClock.view, largeClock.view)
@@ -85,11 +89,13 @@
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
+ val seedColor: Int?,
) : ClockFaceController {
// MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -104,11 +110,16 @@
}
init {
+ if (seedColor != null) {
+ currentColor = seedColor
+ }
view.setColors(currentColor, currentColor)
}
override val events =
object : ClockFaceEvents {
+ override fun onTimeTick() = view.refreshTime()
+
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
@@ -129,7 +140,9 @@
fun updateColor() {
val color =
- if (isRegionDark) {
+ if (seedColor != null) {
+ seedColor
+ } else if (isRegionDark) {
resources.getColor(android.R.color.system_accent1_100)
} else {
resources.getColor(android.R.color.system_accent2_600)
@@ -149,7 +162,8 @@
inner class LargeClockFaceController(
view: AnimatableClockView,
- ) : DefaultClockFaceController(view) {
+ seedColor: Int?,
+ ) : DefaultClockFaceController(view, seedColor) {
override fun recomputePadding(targetRegion: Rect?) {
// We center the view within the targetRegion instead of within the parent
// view by computing the difference and adding that to the padding.
@@ -169,8 +183,6 @@
}
inner class DefaultClockEvents : ClockEvents {
- override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 4c0504b..0fd1b49 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -22,6 +22,7 @@
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockSettings
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_NAME = "Default Clock"
@@ -36,12 +37,12 @@
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
- override fun createClock(id: ClockId): ClockController {
- if (id != DEFAULT_CLOCK_ID) {
- throw IllegalArgumentException("$id is unsupported by $TAG")
+ override fun createClock(settings: ClockSettings): ClockController {
+ if (settings.clockId != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return DefaultClockController(ctx, layoutInflater, resources)
+ return DefaultClockController(ctx, layoutInflater, resources, settings)
}
override fun getClockThumbnail(id: ClockId): Drawable? {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index e4e9c46..c120876 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -181,6 +181,9 @@
/** Flag denoting whether the Wallpaper preview should use the full screen UI. */
const val FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW = "wallpaper_fullscreen_preview"
+ /** Flag denoting whether the Monochromatic Theme is enabled. */
+ const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled"
+
object Columns {
/** String. Unique ID for the flag. */
const val NAME = "name"
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
index 813b22a..91f3309 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
@@ -57,19 +57,19 @@
public static final DynamicColor surfaceDim =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 87.0);
- public static final DynamicColor surfaceSub2 =
+ public static final DynamicColor surfaceContainerLowest =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 4.0 : 100.0);
- public static final DynamicColor surfaceSub1 =
+ public static final DynamicColor surfaceContainerLow =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 96.0);
public static final DynamicColor surfaceContainer =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 12.0 : 94.0);
- public static final DynamicColor surfaceAdd1 =
+ public static final DynamicColor surfaceContainerHigh =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 17.0 : 92.0);
- public static final DynamicColor surfaceAdd2 =
+ public static final DynamicColor surfaceContainerHighest =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 22.0 : 90.0);
public static final DynamicColor onSurface =
@@ -366,6 +366,27 @@
public static final DynamicColor textHintInverse =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
+ public static final DynamicColor primaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.primaryPalette, (s) -> s.primaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor secondaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.secondaryPalette, (s) -> s.secondaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor tertiaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.tertiaryPalette, (s) -> s.tertiaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor neutralPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.neutralPalette, (s) -> s.neutralPalette.getKeyColor().getTone());
+
+ public static final DynamicColor neutralVariantPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.neutralVariantPalette,
+ (s) -> s.neutralVariantPalette.getKeyColor().getTone());
+
private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) {
return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0);
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
index 6911666..8e905ea 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
@@ -26,6 +26,7 @@
*/
public final class TonalPalette {
Map<Integer, Integer> cache;
+ Hct keyColor;
double hue;
double chroma;
@@ -46,7 +47,7 @@
* @return Tones matching that color's hue and chroma.
*/
public static TonalPalette fromHct(Hct hct) {
- return TonalPalette.fromHueAndChroma(hct.getHue(), hct.getChroma());
+ return new TonalPalette(hct.getHue(), hct.getChroma(), hct);
}
/**
@@ -57,13 +58,52 @@
* @return Tones matching hue and chroma.
*/
public static TonalPalette fromHueAndChroma(double hue, double chroma) {
- return new TonalPalette(hue, chroma);
+ return new TonalPalette(hue, chroma, createKeyColor(hue, chroma));
}
- private TonalPalette(double hue, double chroma) {
+ private TonalPalette(double hue, double chroma, Hct keyColor) {
cache = new HashMap<>();
this.hue = hue;
this.chroma = chroma;
+ this.keyColor = keyColor;
+ }
+
+ /** The key color is the first tone, starting from T50, matching the given hue and chroma. */
+ private static Hct createKeyColor(double hue, double chroma) {
+ double startTone = 50.0;
+ Hct smallestDeltaHct = Hct.from(hue, chroma, startTone);
+ double smallestDelta = Math.abs(smallestDeltaHct.getChroma() - chroma);
+ // Starting from T50, check T+/-delta to see if they match the requested
+ // chroma.
+ //
+ // Starts from T50 because T50 has the most chroma available, on
+ // average. Thus it is most likely to have a direct answer and minimize
+ // iteration.
+ for (double delta = 1.0; delta < 50.0; delta += 1.0) {
+ // Termination condition rounding instead of minimizing delta to avoid
+ // case where requested chroma is 16.51, and the closest chroma is 16.49.
+ // Error is minimized, but when rounded and displayed, requested chroma
+ // is 17, key color's chroma is 16.
+ if (Math.round(chroma) == Math.round(smallestDeltaHct.getChroma())) {
+ return smallestDeltaHct;
+ }
+
+ final Hct hctAdd = Hct.from(hue, chroma, startTone + delta);
+ final double hctAddDelta = Math.abs(hctAdd.getChroma() - chroma);
+ if (hctAddDelta < smallestDelta) {
+ smallestDelta = hctAddDelta;
+ smallestDeltaHct = hctAdd;
+ }
+
+ final Hct hctSubtract = Hct.from(hue, chroma, startTone - delta);
+ final double hctSubtractDelta = Math.abs(hctSubtract.getChroma() - chroma);
+ if (hctSubtractDelta < smallestDelta) {
+ smallestDelta = hctSubtractDelta;
+ smallestDeltaHct = hctSubtract;
+ }
+ }
+
+ return smallestDeltaHct;
}
/**
@@ -98,4 +138,9 @@
public double getHue() {
return this.hue;
}
+
+ /** The key color is the first tone, starting from T50, that matches the palette's chroma. */
+ public Hct getKeyColor() {
+ return this.keyColor;
+ }
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
index e7c6f79..234826e 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
@@ -34,14 +34,16 @@
isDark,
contrastLevel,
TonalPalette.fromHueAndChroma(
- MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 120.0), 40.0),
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 240.0), 40.0),
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
24.0),
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
32.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
+ TonalPalette.fromHueAndChroma(
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 8.0),
+ TonalPalette.fromHueAndChroma(
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 12.0));
}
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
index cd74ac3..8480684 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
@@ -28,11 +28,11 @@
Variant.TONAL_SPOT,
isDark,
contrastLevel,
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 40.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
TonalPalette.fromHueAndChroma(
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 6.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 4.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0));
}
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
index 89337ca..160fab7 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
@@ -38,7 +38,7 @@
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
32.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index a2a0709..bbfb6fb 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,11 +17,13 @@
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
+import com.android.internal.annotations.Keep
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
+import org.json.JSONObject
/** Identifies a clock design */
typealias ClockId = String
@@ -41,7 +43,13 @@
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- fun createClock(id: ClockId): ClockController
+ @Deprecated("Use overload with ClockSettings")
+ fun createClock(id: ClockId): ClockController {
+ return createClock(ClockSettings(id, null, null))
+ }
+
+ /** Initializes and returns the target clock design */
+ fun createClock(settings: ClockSettings): ClockController
/** A static thumbnail for rendering in some examples */
fun getClockThumbnail(id: ClockId): Drawable?
@@ -62,11 +70,16 @@
val animations: ClockAnimations
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ fun initialize(
+ resources: Resources,
+ dozeFraction: Float,
+ foldFraction: Float,
+ ) {
events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
/** Optional method for dumping debug information */
@@ -87,9 +100,6 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
- /** Call every time tick */
- fun onTimeTick() {}
-
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) {}
@@ -131,6 +141,13 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
+ /** Call every time tick */
+ fun onTimeTick() {}
+
+ /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+ val tickRate: ClockTickRate
+ get() = ClockTickRate.PER_MINUTE
+
/** Region Darkness specific to the clock face */
fun onRegionDarknessChanged(isDark: Boolean) {}
@@ -150,8 +167,46 @@
fun onTargetRegionChanged(targetRegion: Rect?) {}
}
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+ PER_MINUTE(2), // Update the clock once per minute.
+ PER_SECOND(1), // Update the clock once per second.
+ PER_FRAME(0), // Update the clock every second.
+}
+
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
val name: String,
)
+
+/** Structure for keeping clock-specific settings */
+@Keep
+data class ClockSettings(
+ var clockId: ClockId? = null,
+ var seedColor: Int? = null,
+ var _applied_timestamp: Long? = null,
+) {
+ companion object {
+ private val KEY_CLOCK_ID = "clockId"
+ private val KEY_SEED_COLOR = "seedColor"
+ private val KEY_TIMESTAMP = "_applied_timestamp"
+
+ fun serialize(setting: ClockSettings): String {
+ return JSONObject()
+ .put(KEY_CLOCK_ID, setting.clockId)
+ .put(KEY_SEED_COLOR, setting.seedColor)
+ .put(KEY_TIMESTAMP, setting._applied_timestamp)
+ .toString()
+ }
+
+ fun deserialize(jsonStr: String): ClockSettings {
+ val json = JSONObject(jsonStr)
+ return ClockSettings(
+ json.getString(KEY_CLOCK_ID),
+ if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
+ if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
new file mode 100644
index 0000000..85ec42d
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
@@ -0,0 +1,85 @@
+package com.android.systemui.statusbar
+
+import android.os.Bundle
+
+class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) {
+ companion object {
+ private const val TAG = "Weather"
+ private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key"
+ private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key"
+ private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key"
+ private const val INVALID_TEMPERATURE = Int.MIN_VALUE
+
+ fun fromBundle(extras: Bundle): Weather? {
+ val icon =
+ WeatherStateIcon.fromInt(
+ extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id)
+ )
+ if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) {
+ return null
+ }
+ val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE)
+ if (temperature == INVALID_TEMPERATURE) {
+ return null
+ }
+ return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY))
+ }
+ }
+
+ enum class WeatherStateIcon(val id: Int) {
+ UNKNOWN_ICON(0),
+
+ // Clear, day & night.
+ SUNNY(1),
+ CLEAR_NIGHT(2),
+
+ // Mostly clear, day & night.
+ MOSTLY_SUNNY(3),
+ MOSTLY_CLEAR_NIGHT(4),
+
+ // Partly cloudy, day & night.
+ PARTLY_CLOUDY(5),
+ PARTLY_CLOUDY_NIGHT(6),
+
+ // Mostly cloudy, day & night.
+ MOSTLY_CLOUDY_DAY(7),
+ MOSTLY_CLOUDY_NIGHT(8),
+ CLOUDY(9),
+ HAZE_FOG_DUST_SMOKE(10),
+ DRIZZLE(11),
+ HEAVY_RAIN(12),
+ SHOWERS_RAIN(13),
+
+ // Scattered showers, day & night.
+ SCATTERED_SHOWERS_DAY(14),
+ SCATTERED_SHOWERS_NIGHT(15),
+
+ // Isolated scattered thunderstorms, day & night.
+ ISOLATED_SCATTERED_TSTORMS_DAY(16),
+ ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
+ STRONG_TSTORMS(18),
+ BLIZZARD(19),
+ BLOWING_SNOW(20),
+ FLURRIES(21),
+ HEAVY_SNOW(22),
+
+ // Scattered snow showers, day & night.
+ SCATTERED_SNOW_SHOWERS_DAY(23),
+ SCATTERED_SNOW_SHOWERS_NIGHT(24),
+ SNOW_SHOWERS_SNOW(25),
+ MIXED_RAIN_HAIL_RAIN_SLEET(26),
+ SLEET_HAIL(27),
+ TORNADO(28),
+ TROPICAL_STORM_HURRICANE(29),
+ WINDY_BREEZY(30),
+ WINTRY_MIX_RAIN_SNOW(31);
+
+ companion object {
+ fun fromInt(value: Int) = values().firstOrNull { it.id == value }
+ }
+ }
+
+ override fun toString(): String {
+ return "$conditions $temperature${if (isCelsius) "C" else "F"}"
+ }
+}
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
new file mode 100644
index 0000000..a179c14
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorAccentSecondary"/>
+ <corners android:radius="10dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
new file mode 100644
index 0000000..dad2cdf8e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="24dp"
+ android:viewportWidth="56"
+ android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M12 0H44C50.6274 0 56 5.37258 56 12C56 18.6274 50.6274 24 44 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z"
+ />
+ <path
+ android:pathData="M0 0V24H56V0"
+ android:fillColor="#FFFFFF"
+ />
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_content_paste.xml b/packages/SystemUI/res/drawable/ic_content_paste.xml
new file mode 100644
index 0000000..8c8b81e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_content_paste.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,42Q7.7,42 6.85,41.15Q6,40.3 6,39V9Q6,7.7 6.85,6.85Q7.7,6 9,6H19.1Q19.45,4.25 20.825,3.125Q22.2,2 24,2Q25.8,2 27.175,3.125Q28.55,4.25 28.9,6H39Q40.3,6 41.15,6.85Q42,7.7 42,9V39Q42,40.3 41.15,41.15Q40.3,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H36V13.5H12V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM24,9Q24.85,9 25.425,8.425Q26,7.85 26,7Q26,6.15 25.425,5.575Q24.85,5 24,5Q23.15,5 22.575,5.575Q22,6.15 22,7Q22,7.85 22.575,8.425Q23.15,9 24,9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index eec3b11..9b01bd8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -125,6 +125,45 @@
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
+ <LinearLayout
+ android:id="@+id/minimized_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:elevation="7dp"
+ android:padding="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:background="@drawable/clipboard_minimized_background">
+ <ImageView
+ android:src="@drawable/ic_content_paste"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"/>
+ <ImageView
+ android:src="@*android:drawable/ic_chevron_end"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:paddingEnd="-8dp"
+ android:paddingStart="-4dp"/>
+ </LinearLayout>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
<FrameLayout
android:id="@+id/dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
@@ -132,10 +171,10 @@
android:elevation="10dp"
android:visibility="gone"
android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
android:contentDescription="@string/clipboard_dismiss_description">
<ImageView
android:id="@+id/dismiss_image"
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index ee3adba..ded6f93 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -91,7 +91,6 @@
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin"
android:background="@drawable/controls_panel_background"
- android:padding="@dimen/global_actions_side_margin"
android:visibility="gone"
/>
</merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 885e5e2..50dcaf3 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -111,5 +111,15 @@
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" />
+ <ImageView
+ android:id="@+id/dream_overlay_assistant_attention_indicator"
+ android:layout_width="@dimen/dream_overlay_grey_chip_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/dream_overlay_assistant_attention_indicator"
+ android:visibility="gone"
+ android:contentDescription=
+ "@string/dream_overlay_status_bar_assistant_attention_indicator" />
+
</LinearLayout>
</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
index 101fad9..c54c4e4 100644
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -47,7 +47,8 @@
android:singleLine="true"
android:textSize="12sp"
android:gravity="top"
- android:layout_gravity="bottom"/>
+ android:layout_gravity="bottom"
+ android:importantForAccessibility="no"/>
<!-- Album name -->
<TextView
@@ -61,5 +62,26 @@
android:singleLine="true"
android:textSize="11sp"
android:gravity="center_vertical"
- android:layout_gravity="bottom"/>
+ android:layout_gravity="bottom"
+ android:importantForAccessibility="no"/>
+
+ <!-- Seek Bar -->
+ <SeekBar
+ android:id="@+id/media_progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="12dp"
+ android:layout_gravity="bottom"
+ android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
+ android:thumb="@android:color/transparent"
+ android:splitTrack="false"
+ android:clickable="false"
+ android:progressTint="?android:attr/textColorPrimary"
+ android:progressBackgroundTint="?android:attr/textColorTertiary"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_info_spacing"/>
</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 375bd39..0ca154e 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -46,4 +46,7 @@
<!-- Whether to tint the icon of the sensor hardware privacy toggle unblock dialog.
Set to false if using a custom icon. -->
<bool name="config_unblockHwSensorIconEnableTint">true</bool>
+
+ <!-- Configuration to set Learn more in device logs as URL link -->
+ <bool name="log_access_confirmation_learn_more_as_link">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
index f30b73e..86106e6 100644
--- a/packages/SystemUI/res/values-television/strings.xml
+++ b/packages/SystemUI/res/values-television/strings.xml
@@ -17,11 +17,6 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
- <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
- \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
- </string>
-
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" translatable="false"></string>
+ <string name="log_access_confirmation_learn_more_url" translatable="false"></string>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f5db5ec..034f145 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -835,4 +835,7 @@
<!-- Whether the floating rotation button should be on the left/right in the device's natural
orientation -->
<bool name="floating_rotation_button_position_left">true</bool>
+
+ <!-- Configuration to set Learn more in device logs as URL link -->
+ <bool name="log_access_confirmation_learn_more_as_link">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0f94628..943844f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2798,6 +2798,8 @@
<string name="dream_overlay_status_bar_mic_off">Mic is off</string>
<!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
+ <!-- Content description for the assistant attention indicator [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_assistant_attention_indicator">Assistant is listening</string>
<!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_notification_indicator">{count, plural,
=1 {# notification}
@@ -2844,7 +2846,9 @@
</string>
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
+ <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android?p=system_logs#topic=7313011</string>
+ <string name="log_access_confirmation_learn_more">Learn more</string>
+ <string name="log_access_confirmation_learn_more_at">Learn more at <xliff:g id="url" example="g.co/android/devicelogs">%s</xliff:g></string>
<!--
Template for an action that opens a specific app. [CHAR LIMIT=16]
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 3a940e9..7f95ca6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
+import android.view.ViewTreeObserver
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -42,12 +43,15 @@
import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -72,7 +76,7 @@
private val configurationController: ConfigurationController,
@Main private val resources: Resources,
private val context: Context,
- @Main private val mainExecutor: Executor,
+ @Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
@KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
@@ -92,8 +96,11 @@
if (regionSamplingEnabled) {
clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+ } else {
+ updateColors()
}
updateFontSizes()
+ updateTimeListeners()
}
}
@@ -208,6 +215,10 @@
}
var regionSampler: RegionSampler? = null
+ var smallTimeListener: TimeListener? = null
+ var largeTimeListener: TimeListener? = null
+ val shouldTimeListenerRun: Boolean
+ get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
private var smallClockIsDark = true
private var largeClockIsDark = true
@@ -246,6 +257,9 @@
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
+
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
override fun onTimeFormatChanged(timeFormat: String) {
@@ -285,6 +299,8 @@
}
}
}
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
fun unregisterListeners() {
@@ -299,6 +315,25 @@
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
regionSampler?.stopRegionSampler()
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+ }
+
+ private fun updateTimeListeners() {
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+
+ smallTimeListener = null
+ largeTimeListener = null
+
+ clock?.smallClock?.let {
+ smallTimeListener = TimeListener(it, mainExecutor)
+ smallTimeListener?.update(shouldTimeListenerRun)
+ }
+ clock?.largeClock?.let {
+ largeTimeListener = TimeListener(it, mainExecutor)
+ largeTimeListener?.update(shouldTimeListenerRun)
+ }
}
private fun updateFontSizes() {
@@ -308,12 +343,18 @@
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
}
+ private fun handleDoze(doze: Float) {
+ dozeAmount = doze
+ clock?.animations?.doze(dozeAmount)
+ smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ }
+
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch {
keyguardInteractor.dozeAmount.collect {
- dozeAmount = it
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it)
}
}
}
@@ -322,8 +363,7 @@
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor.dozeAmountTransition.collect {
- dozeAmount = it.value
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it.value)
}
}
}
@@ -338,8 +378,7 @@
keyguardTransitionInteractor.anyStateToAodTransition.filter {
it.transitionState == TransitionState.FINISHED
}.collect {
- dozeAmount = 1f
- clock?.animations?.doze(dozeAmount)
+ handleDoze(1f)
}
}
}
@@ -359,7 +398,54 @@
}
}
+ class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
+ val predrawListener = ViewTreeObserver.OnPreDrawListener {
+ clockFace.events.onTimeTick()
+ true
+ }
+
+ val secondsRunnable = object : Runnable {
+ override fun run() {
+ if (!isRunning) {
+ return
+ }
+
+ executor.executeDelayed(this, 990)
+ clockFace.events.onTimeTick()
+ }
+ }
+
+ var isRunning: Boolean = false
+ private set
+
+ fun start() {
+ if (isRunning) {
+ return
+ }
+
+ isRunning = true
+ when (clockFace.events.tickRate) {
+ ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+ ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
+ ClockTickRate.PER_FRAME -> {
+ clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
+ clockFace.view.invalidate()
+ }
+ }
+ }
+
+ fun stop() {
+ if (!isRunning) { return }
+
+ isRunning = false
+ clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
+ }
+
+ fun update(shouldRun: Boolean) = if (shouldRun) start() else stop()
+ }
+
companion object {
private val TAG = ClockEventController::class.simpleName!!
+ private val DOZE_TICKRATE_THRESHOLD = 0.99f
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dd42c8e..3eec565 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -344,7 +344,8 @@
}
ClockController clock = getClock();
if (clock != null) {
- clock.getEvents().onTimeTick();
+ clock.getSmallClock().getEvents().onTimeTick();
+ clock.getLargeClock().getEvents().onTimeTick();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 3e0fa45..54939fd 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,7 +48,8 @@
pos: Int,
val statusBarStateController: StatusBarStateController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- val mainExecutor: Executor
+ val mainExecutor: Executor,
+ val logger: ScreenDecorationsLogger,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -55,6 +57,7 @@
private var rimAnimator: AnimatorSet? = null
private val rimRect = RectF()
private var cameraProtectionColor = Color.BLACK
+
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
@@ -175,15 +178,22 @@
}
if (showScanningAnim) {
// Make sure that our measured height encompasses the extra space for the animation
- mTotalBounds.union(mBoundingRect)
+ mTotalBounds.set(mBoundingRect)
mTotalBounds.union(
rimRect.left.toInt(),
rimRect.top.toInt(),
rimRect.right.toInt(),
rimRect.bottom.toInt())
- setMeasuredDimension(
- resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+ val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0)
+ val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)
+ logger.boundingRect(rimRect, "onMeasure: Face scanning animation")
+ logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect")
+ logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds")
+ logger.onMeasureDimensions(widthMeasureSpec,
+ heightMeasureSpec,
+ measuredWidth,
+ measuredHeight)
+ setMeasuredDimension(measuredWidth, measuredHeight)
} else {
setMeasuredDimension(
resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 71f98fa..c1c7f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -64,6 +64,7 @@
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -75,6 +76,7 @@
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -120,6 +122,18 @@
R.id.display_cutout_right,
R.id.display_cutout_bottom
};
+ private final ScreenDecorationsLogger mLogger;
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onFaceSensorLocationChanged() {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(new Integer[]{mFaceScanningViewId}));
+ }
+ }
+ };
private DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -153,6 +167,7 @@
private WindowManager mWindowManager;
private int mRotation;
private SettingObserver mColorInversionSetting;
+ @Nullable
private DelayableExecutor mExecutor;
private Handler mHandler;
boolean mPendingConfigChange;
@@ -172,6 +187,7 @@
DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
mFaceScanningViewId);
if (overlay != null) {
+ mLogger.cameraProtectionBoundsForScanningOverlay(bounds);
overlay.setProtection(protectionPath, bounds);
overlay.enableShowProtection(true);
updateOverlayWindowVisibilityIfViewExists(
@@ -184,6 +200,7 @@
}
if (mScreenDecorHwcLayer != null) {
+ mLogger.hwcLayerCameraProtectionBounds(bounds);
mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
mScreenDecorHwcLayer.enableShowProtection(true);
return;
@@ -197,11 +214,12 @@
}
++setProtectionCnt;
final DisplayCutoutView dcv = (DisplayCutoutView) view;
+ mLogger.dcvCameraBounds(id, bounds);
dcv.setProtection(protectionPath, bounds);
dcv.enableShowProtection(true);
}
if (setProtectionCnt == 0) {
- Log.e(TAG, "CutoutView not initialized showCameraProtection");
+ mLogger.cutoutViewNotInitialized();
}
}
@@ -307,7 +325,9 @@
PrivacyDotViewController dotViewController,
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
- FaceScanningProviderFactory faceScanningFactory) {
+ FaceScanningProviderFactory faceScanningFactory,
+ ScreenDecorationsLogger logger,
+ AuthController authController) {
mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
@@ -319,6 +339,8 @@
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
+ mLogger = logger;
+ authController.addCallback(mAuthControllerCallback);
}
@Override
@@ -1306,7 +1328,7 @@
if (showProtection) {
// Make sure that our measured height encompasses the protection
- mTotalBounds.union(mBoundingRect);
+ mTotalBounds.set(mBoundingRect);
mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
(int) protectionRect.right, (int) protectionRect.bottom);
setMeasuredDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
deleted file mode 100644
index 81d7766..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-
-import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
-import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;
-
-import android.annotation.FloatRange;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.List;
-
-/**
- * Contains logic for an accessibility floating menu view.
- */
-public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
- private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1;
- private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_OPACITY_VALUE = 0.55f;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_POSITION_Y_PERCENT = 0.77f;
-
- private final Context mContext;
- private final SecureSettings mSecureSettings;
- private final AccessibilityFloatingMenuView mMenuView;
- private final MigrationTooltipView mMigrationTooltipView;
- private final DockTooltipView mDockTooltipView;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
-
- private final ContentObserver mContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
- }
- };
-
- private final ContentObserver mSizeContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.setSizeType(getSizeType());
- }
- };
-
- private final ContentObserver mFadeOutContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.updateOpacityWith(isFadeEffectEnabled(),
- getOpacityValue());
- }
- };
-
- private final ContentObserver mEnabledA11yServicesContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.onEnabledFeaturesChanged();
- }
- };
-
- public AccessibilityFloatingMenu(Context context, SecureSettings secureSettings) {
- mContext = context;
- mSecureSettings = secureSettings;
- mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
- mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @VisibleForTesting
- AccessibilityFloatingMenu(Context context, SecureSettings secureSettings,
- AccessibilityFloatingMenuView menuView) {
- mContext = context;
- mSecureSettings = secureSettings;
- mMenuView = menuView;
- mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @Override
- public boolean isShowing() {
- return mMenuView.isShowing();
- }
-
- @Override
- public void show() {
- if (isShowing()) {
- return;
- }
- final List<AccessibilityTarget> targetList = getTargets(mContext, ACCESSIBILITY_BUTTON);
- if (targetList.isEmpty()) {
- return;
- }
-
- mMenuView.show();
- mMenuView.onTargetsChanged(targetList);
- mMenuView.updateOpacityWith(isFadeEffectEnabled(),
- getOpacityValue());
- mMenuView.setSizeType(getSizeType());
- mMenuView.setShapeType(getShapeType());
- mMenuView.setOnDragEndListener(this::onDragEnd);
-
- showMigrationTooltipIfNecessary();
-
- registerContentObservers();
- }
-
- @Override
- public void hide() {
- if (!isShowing()) {
- return;
- }
-
- mMenuView.hide();
- mMenuView.setOnDragEndListener(null);
- mMigrationTooltipView.hide();
- mDockTooltipView.hide();
-
- unregisterContentObservers();
- }
-
- @NonNull
- private Position getPosition(Context context) {
- final String absolutePositionString = Prefs.getString(context,
- Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
-
- if (TextUtils.isEmpty(absolutePositionString)) {
- return new Position(DEFAULT_POSITION_X_PERCENT, DEFAULT_POSITION_Y_PERCENT);
- } else {
- return Position.fromString(absolutePositionString);
- }
- }
-
- // Migration tooltip was the android S feature. It's just used on the Android version from R
- // to S. In addition, it only shows once.
- private void showMigrationTooltipIfNecessary() {
- if (isMigrationTooltipPromptEnabled()) {
- mMigrationTooltipView.show();
-
- mSecureSettings.putInt(
- ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0);
- }
- }
-
- private boolean isMigrationTooltipPromptEnabled() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
- DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
- }
-
- private void onDragEnd(Position position) {
- SysUiStatsLog.write(SysUiStatsLog.ACCESSIBILITY_FLOATING_MENU_UI_CHANGED,
- position.getPercentageX(), position.getPercentageY(),
- mContext.getResources().getConfiguration().orientation);
- savePosition(mContext, position);
- showDockTooltipIfNecessary(mContext);
- }
-
- private void savePosition(Context context, Position position) {
- Prefs.putString(context, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
- position.toString());
- }
-
- /**
- * Shows tooltip when user drags accessibility floating menu for the first time.
- */
- private void showDockTooltipIfNecessary(Context context) {
- if (!Prefs.get(context).getBoolean(
- HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, false)) {
- // if the menu is an oval, the user has already dragged it out, so show the tooltip.
- if (mMenuView.isOvalShape()) {
- mDockTooltipView.show();
- }
-
- Prefs.putBoolean(context, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true);
- }
- }
-
- private boolean isFadeEffectEnabled() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
- DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1;
- }
-
- private float getOpacityValue() {
- return mSecureSettings.getFloat(
- ACCESSIBILITY_FLOATING_MENU_OPACITY,
- DEFAULT_OPACITY_VALUE);
- }
-
- private int getSizeType() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
- }
-
- private int getShapeType() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
- ShapeType.OVAL);
- }
-
- private void registerContentObservers() {
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- /* notifyForDescendants */ false, mContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- /* notifyForDescendants */ false, mContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
- /* notifyForDescendants */ false, mSizeContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
- /* notifyForDescendants */ false, mFadeOutContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
- /* notifyForDescendants */ false, mFadeOutContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- /* notifyForDescendants */ false,
- mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
- }
-
- private void unregisterContentObservers() {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- mSecureSettings.unregisterContentObserver(mSizeContentObserver);
- mSecureSettings.unregisterContentObserver(mFadeOutContentObserver);
- mSecureSettings.unregisterContentObserver(
- mEnabledA11yServicesContentObserver);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 2c413a2..5924149 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -19,8 +19,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS;
-
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.UserHandle;
@@ -38,7 +36,6 @@
import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -59,7 +56,7 @@
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
private final AccessibilityManager mAccessibilityManager;
- private final FeatureFlags mFeatureFlags;
+
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -105,7 +102,6 @@
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- FeatureFlags featureFlags,
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
@@ -115,7 +111,6 @@
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mFeatureFlags = featureFlags;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
@@ -162,7 +157,7 @@
* Handles the accessibility floating menu visibility with the given values.
*
* @param keyguardVisible the keyguard visibility status. Not show the
- * {@link AccessibilityFloatingMenu} when keyguard appears.
+ * {@link MenuView} when keyguard appears.
* @param mode accessibility button mode {@link AccessibilityButtonMode}
* @param targets accessibility button list; it should comes from
* {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
@@ -187,16 +182,12 @@
private void showFloatingMenu() {
if (mFloatingMenu == null) {
- if (mFeatureFlags.isEnabled(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS)) {
- final Display defaultDisplay = mDisplayManager.getDisplay(
- mDisplayTracker.getDefaultDisplayId());
- final Context windowContext = mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
- mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
- mAccessibilityManager, mSecureSettings);
- } else {
- mFloatingMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings);
- }
+ final Display defaultDisplay = mDisplayManager.getDisplay(
+ mDisplayTracker.getDefaultDisplayId());
+ final Context windowContext = mContext.createWindowContext(defaultDisplay,
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
+ mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
}
mFloatingMenu.show();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
deleted file mode 100644
index aff0b1f..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ /dev/null
@@ -1,921 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.util.MathUtils.constrain;
-import static android.util.MathUtils.sq;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.systemBars;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.FloatRange;
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.animation.Animation;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.TranslateAnimation;
-import android.widget.FrameLayout;
-
-import androidx.annotation.DimenRes;
-import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Accessibility floating menu is used for the actions of accessibility features, it's also the
- * action set.
- *
- * <p>The number of items would depend on strings key
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
- */
-public class AccessibilityFloatingMenuView extends FrameLayout
- implements RecyclerView.OnItemTouchListener {
- private static final int INDEX_MENU_ITEM = 0;
- private static final int FADE_OUT_DURATION_MS = 1000;
- private static final int FADE_EFFECT_DURATION_MS = 3000;
- private static final int SNAP_TO_LOCATION_DURATION_MS = 150;
- private static final int MIN_WINDOW_Y = 0;
-
- private static final int ANIMATION_START_OFFSET = 600;
- private static final int ANIMATION_DURATION_MS = 600;
- private static final float ANIMATION_TO_X_VALUE = 0.5f;
-
- private boolean mIsFadeEffectEnabled;
- private boolean mIsShowing;
- private boolean mIsDownInEnlargedTouchArea;
- private boolean mIsDragging = false;
- @Alignment
- private int mAlignment;
- @SizeType
- private int mSizeType = SizeType.SMALL;
- @VisibleForTesting
- @ShapeType
- int mShapeType = ShapeType.OVAL;
- private int mTemporaryShapeType;
- @RadiusType
- private int mRadiusType;
- private int mMargin;
- private int mPadding;
- // The display width excludes the window insets of the system bar and display cutout.
- private int mDisplayHeight;
- // The display Height excludes the window insets of the system bar and display cutout.
- private int mDisplayWidth;
- private int mIconWidth;
- private int mIconHeight;
- private int mInset;
- private int mDownX;
- private int mDownY;
- private int mRelativeToPointerDownX;
- private int mRelativeToPointerDownY;
- private float mRadius;
- private final Rect mDisplayInsetsRect = new Rect();
- private final Rect mImeInsetsRect = new Rect();
- private final Position mPosition;
- private float mSquareScaledTouchSlop;
- private final Configuration mLastConfiguration;
- private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty();
- private final RecyclerView mListView;
- private final AccessibilityTargetAdapter mAdapter;
- private float mFadeOutValue;
- private final ValueAnimator mFadeOutAnimator;
- @VisibleForTesting
- final ValueAnimator mDragAnimator;
- private final Handler mUiHandler;
- @VisibleForTesting
- final WindowManager.LayoutParams mCurrentLayoutParams;
- private final WindowManager mWindowManager;
- private final List<AccessibilityTarget> mTargets = new ArrayList<>();
-
- @IntDef({
- SizeType.SMALL,
- SizeType.LARGE
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface SizeType {
- int SMALL = 0;
- int LARGE = 1;
- }
-
- @IntDef({
- ShapeType.OVAL,
- ShapeType.HALF_OVAL
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ShapeType {
- int OVAL = 0;
- int HALF_OVAL = 1;
- }
-
- @IntDef({
- RadiusType.LEFT_HALF_OVAL,
- RadiusType.OVAL,
- RadiusType.RIGHT_HALF_OVAL
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface RadiusType {
- int LEFT_HALF_OVAL = 0;
- int OVAL = 1;
- int RIGHT_HALF_OVAL = 2;
- }
-
- @IntDef({
- Alignment.LEFT,
- Alignment.RIGHT
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface Alignment {
- int LEFT = 0;
- int RIGHT = 1;
- }
-
- /**
- * Interface for a callback to be invoked when the floating menu was dragging.
- */
- interface OnDragEndListener {
-
- /**
- * Called when a drag is completed.
- *
- * @param position Stores information about the position
- */
- void onDragEnd(Position position);
- }
-
- public AccessibilityFloatingMenuView(Context context, @NonNull Position position) {
- this(context, position, new RecyclerView(context));
- }
-
- @VisibleForTesting
- AccessibilityFloatingMenuView(Context context, @NonNull Position position,
- RecyclerView listView) {
- super(context);
-
- mListView = listView;
- mWindowManager = context.getSystemService(WindowManager.class);
- mLastConfiguration = new Configuration(getResources().getConfiguration());
- mAdapter = new AccessibilityTargetAdapter(mTargets);
- mUiHandler = createUiHandler();
- mPosition = position;
- mAlignment = transformToAlignment(mPosition.getPercentageX());
- mRadiusType = (mAlignment == Alignment.RIGHT)
- ? RadiusType.LEFT_HALF_OVAL
- : RadiusType.RIGHT_HALF_OVAL;
-
- updateDimensions();
-
- mCurrentLayoutParams = createDefaultLayoutParams();
-
- mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
- mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
- mFadeOutAnimator.addUpdateListener(
- (animation) -> setAlpha((float) animation.getAnimatedValue()));
-
- mDragAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mDragAnimator.setDuration(SNAP_TO_LOCATION_DURATION_MS);
- mDragAnimator.setInterpolator(new OvershootInterpolator());
- mDragAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPosition.update(transformCurrentPercentageXToEdge(),
- calculateCurrentPercentageY());
- mAlignment = transformToAlignment(mPosition.getPercentageX());
-
- updateLocationWith(mPosition);
-
- updateInsetWith(getResources().getConfiguration().uiMode, mAlignment);
-
- mRadiusType = (mAlignment == Alignment.RIGHT)
- ? RadiusType.LEFT_HALF_OVAL
- : RadiusType.RIGHT_HALF_OVAL;
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
-
- fadeOut();
-
- mOnDragEndListener.ifPresent(
- onDragEndListener -> onDragEndListener.onDragEnd(mPosition));
- }
- });
-
-
- initListView();
- updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
- }
-
- @Override
- public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
- @NonNull MotionEvent event) {
- final int currentRawX = (int) event.getRawX();
- final int currentRawY = (int) event.getRawY();
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- fadeIn();
-
- mDownX = currentRawX;
- mDownY = currentRawY;
- mRelativeToPointerDownX = mCurrentLayoutParams.x - mDownX;
- mRelativeToPointerDownY = mCurrentLayoutParams.y - mDownY;
- mListView.animate().translationX(0);
- break;
- case MotionEvent.ACTION_MOVE:
- if (mIsDragging
- || hasExceededTouchSlop(mDownX, mDownY, currentRawX, currentRawY)) {
- if (!mIsDragging) {
- mIsDragging = true;
- setRadius(mRadius, RadiusType.OVAL);
- setInset(0, 0);
- }
-
- mTemporaryShapeType =
- isMovingTowardsScreenEdge(mAlignment, currentRawX, mDownX)
- ? ShapeType.HALF_OVAL
- : ShapeType.OVAL;
- final int newWindowX = currentRawX + mRelativeToPointerDownX;
- final int newWindowY = currentRawY + mRelativeToPointerDownY;
- mCurrentLayoutParams.x =
- constrain(newWindowX, getMinWindowX(), getMaxWindowX());
- mCurrentLayoutParams.y = constrain(newWindowY, MIN_WINDOW_Y, getMaxWindowY());
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mIsDragging) {
- mIsDragging = false;
-
- final int minX = getMinWindowX();
- final int maxX = getMaxWindowX();
- final int endX = mCurrentLayoutParams.x > ((minX + maxX) / 2)
- ? maxX : minX;
- final int endY = mCurrentLayoutParams.y;
- snapToLocation(endX, endY);
-
- setShapeType(mTemporaryShapeType);
-
- // Avoid triggering the listener of the item.
- return true;
- }
-
- // Must switch the oval shape type before tapping the corresponding item in the
- // list view, otherwise it can't work on it.
- if (!isOvalShape()) {
- setShapeType(ShapeType.OVAL);
-
- return true;
- }
-
- fadeOut();
- break;
- default: // Do nothing
- }
-
- // not consume all the events here because keeping the scroll behavior of list view.
- return false;
- }
-
- @Override
- public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
- // Do Nothing
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean b) {
- // Do Nothing
- }
-
- void show() {
- if (isShowing()) {
- return;
- }
-
- mIsShowing = true;
- mWindowManager.addView(this, mCurrentLayoutParams);
-
- setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
- setSystemGestureExclusion();
- }
-
- void hide() {
- if (!isShowing()) {
- return;
- }
-
- mIsShowing = false;
- mDragAnimator.cancel();
- mWindowManager.removeView(this);
-
- setOnApplyWindowInsetsListener(null);
- setSystemGestureExclusion();
- }
-
- boolean isShowing() {
- return mIsShowing;
- }
-
- boolean isOvalShape() {
- return mShapeType == ShapeType.OVAL;
- }
-
- void onTargetsChanged(List<AccessibilityTarget> newTargets) {
- fadeIn();
-
- mTargets.clear();
- mTargets.addAll(newTargets);
- onEnabledFeaturesChanged();
-
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- setSystemGestureExclusion();
-
- fadeOut();
- }
-
- void setSizeType(@SizeType int newSizeType) {
- fadeIn();
-
- mSizeType = newSizeType;
-
- updateItemViewWith(newSizeType);
- updateRadiusWith(newSizeType, mRadiusType, mTargets.size());
-
- // When the icon sized changed, the menu size and location will be impacted.
- updateLocationWith(mPosition);
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- updateOffsetWith(mShapeType, mAlignment);
- setSystemGestureExclusion();
-
- fadeOut();
- }
-
- void setShapeType(@ShapeType int newShapeType) {
- fadeIn();
-
- mShapeType = newShapeType;
-
- updateOffsetWith(newShapeType, mAlignment);
-
- setOnTouchListener(
- newShapeType == ShapeType.OVAL
- ? null
- : (view, event) -> onTouched(event));
-
- fadeOut();
- }
-
- public void setOnDragEndListener(OnDragEndListener onDragEndListener) {
- mOnDragEndListener = Optional.ofNullable(onDragEndListener);
- }
-
- void startTranslateXAnimation() {
- fadeIn();
-
- final float toXValue = (mAlignment == Alignment.RIGHT)
- ? ANIMATION_TO_X_VALUE
- : -ANIMATION_TO_X_VALUE;
- final TranslateAnimation animation =
- new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, toXValue,
- Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, 0);
- animation.setDuration(ANIMATION_DURATION_MS);
- animation.setRepeatMode(Animation.REVERSE);
- animation.setInterpolator(new OvershootInterpolator());
- animation.setRepeatCount(Animation.INFINITE);
- animation.setStartOffset(ANIMATION_START_OFFSET);
- mListView.startAnimation(animation);
- }
-
- void stopTranslateXAnimation() {
- mListView.clearAnimation();
-
- fadeOut();
- }
-
- Rect getWindowLocationOnScreen() {
- final int left = mCurrentLayoutParams.x;
- final int top = mCurrentLayoutParams.y;
- return new Rect(left, top, left + getWindowWidth(), top + getWindowHeight());
- }
-
- void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
- mIsFadeEffectEnabled = isFadeEffectEnabled;
- mFadeOutValue = newOpacityValue;
-
- mFadeOutAnimator.cancel();
- mFadeOutAnimator.setFloatValues(1.0f, mFadeOutValue);
- setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
- }
-
- void onEnabledFeaturesChanged() {
- mAdapter.notifyDataSetChanged();
- }
-
- @VisibleForTesting
- void fadeIn() {
- if (!mIsFadeEffectEnabled) {
- return;
- }
-
- mFadeOutAnimator.cancel();
- mUiHandler.removeCallbacksAndMessages(null);
- mUiHandler.post(() -> setAlpha(/* completely opaque */ 1.0f));
- }
-
- @VisibleForTesting
- void fadeOut() {
- if (!mIsFadeEffectEnabled) {
- return;
- }
-
- mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
- }
-
- private boolean onTouched(MotionEvent event) {
- final int action = event.getAction();
- final int currentX = (int) event.getX();
- final int currentY = (int) event.getY();
-
- final int marginStartEnd = getMarginStartEndWith(mLastConfiguration);
- final Rect touchDelegateBounds =
- new Rect(marginStartEnd, mMargin, marginStartEnd + getLayoutWidth(),
- mMargin + getLayoutHeight());
- if (action == MotionEvent.ACTION_DOWN
- && touchDelegateBounds.contains(currentX, currentY)) {
- mIsDownInEnlargedTouchArea = true;
- }
-
- if (!mIsDownInEnlargedTouchArea) {
- return false;
- }
-
- if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_CANCEL) {
- mIsDownInEnlargedTouchArea = false;
- }
-
- // In order to correspond to the correct item of list view.
- event.setLocation(currentX - mMargin, currentY - mMargin);
- return mListView.dispatchTouchEvent(event);
- }
-
- private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
- final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- final Rect displayWindowInsetsRect = getDisplayInsets(windowMetrics).toRect();
- if (!displayWindowInsetsRect.equals(mDisplayInsetsRect)) {
- updateDisplaySizeWith(windowMetrics);
- updateLocationWith(mPosition);
- }
-
- final Rect imeInsetsRect = windowMetrics.getWindowInsets().getInsets(ime()).toRect();
- if (!imeInsetsRect.equals(mImeInsetsRect)) {
- if (isImeVisible(imeInsetsRect)) {
- mImeInsetsRect.set(imeInsetsRect);
- } else {
- mImeInsetsRect.setEmpty();
- }
-
- updateLocationWith(mPosition);
- }
-
- return insets;
- }
-
- private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) {
- return (side == Alignment.RIGHT && currentRawX > downX)
- || (side == Alignment.LEFT && downX > currentRawX);
- }
-
- private boolean isImeVisible(Rect imeInsetsRect) {
- return imeInsetsRect.left != 0 || imeInsetsRect.top != 0 || imeInsetsRect.right != 0
- || imeInsetsRect.bottom != 0;
- }
-
- private boolean hasExceededTouchSlop(int startX, int startY, int endX, int endY) {
- return (sq(endX - startX) + sq(endY - startY)) > mSquareScaledTouchSlop;
- }
-
- private void setRadius(float radius, @RadiusType int type) {
- getMenuGradientDrawable().setCornerRadii(createRadii(radius, type));
- }
-
- private float[] createRadii(float radius, @RadiusType int type) {
- if (type == RadiusType.LEFT_HALF_OVAL) {
- return new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
- }
-
- if (type == RadiusType.RIGHT_HALF_OVAL) {
- return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f};
- }
-
- return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
- }
-
- private Handler createUiHandler() {
- return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
- }
-
- private void updateDimensions() {
- final Resources res = getResources();
-
- updateDisplaySizeWith(mWindowManager.getCurrentWindowMetrics());
-
- mMargin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
- mInset =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
-
- mSquareScaledTouchSlop =
- sq(ViewConfiguration.get(getContext()).getScaledTouchSlop());
-
- updateItemViewDimensionsWith(mSizeType);
- }
-
- private void updateDisplaySizeWith(WindowMetrics metrics) {
- final Rect displayBounds = metrics.getBounds();
- final Insets displayInsets = getDisplayInsets(metrics);
- mDisplayInsetsRect.set(displayInsets.toRect());
- displayBounds.inset(displayInsets);
- mDisplayWidth = displayBounds.width();
- mDisplayHeight = displayBounds.height();
- }
-
- private void updateItemViewDimensionsWith(@SizeType int sizeType) {
- final Resources res = getResources();
- final int paddingResId =
- sizeType == SizeType.SMALL
- ? R.dimen.accessibility_floating_menu_small_padding
- : R.dimen.accessibility_floating_menu_large_padding;
- mPadding = res.getDimensionPixelSize(paddingResId);
-
- final int iconResId =
- sizeType == SizeType.SMALL
- ? R.dimen.accessibility_floating_menu_small_width_height
- : R.dimen.accessibility_floating_menu_large_width_height;
- mIconWidth = res.getDimensionPixelSize(iconResId);
- mIconHeight = mIconWidth;
- }
-
- private void updateItemViewWith(@SizeType int sizeType) {
- updateItemViewDimensionsWith(sizeType);
-
- mAdapter.setItemPadding(mPadding);
- mAdapter.setIconWidthHeight(mIconWidth);
- mAdapter.notifyDataSetChanged();
- }
-
- private void initListView() {
- final Drawable background =
- getContext().getDrawable(R.drawable.accessibility_floating_menu_background);
- final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
- final LayoutParams layoutParams =
- new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- mListView.setLayoutParams(layoutParams);
- final InstantInsetLayerDrawable layerDrawable =
- new InstantInsetLayerDrawable(new Drawable[]{background});
- mListView.setBackground(layerDrawable);
- mListView.setAdapter(mAdapter);
- mListView.setLayoutManager(layoutManager);
- mListView.addOnItemTouchListener(this);
- mListView.animate().setInterpolator(new OvershootInterpolator());
- mListView.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(mListView) {
- @NonNull
- @Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return new ItemDelegateCompat(this,
- AccessibilityFloatingMenuView.this);
- }
- });
-
- updateListViewWith(mLastConfiguration);
-
- addView(mListView);
- }
-
- private void updateListViewWith(Configuration configuration) {
- updateMarginWith(configuration);
-
- final int elevation =
- getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
- mListView.setElevation(elevation);
- }
-
- private WindowManager.LayoutParams createDefaultLayoutParams() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
- PixelFormat.TRANSLUCENT);
- params.receiveInsetsIgnoringZOrder = true;
- params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
- params.windowAnimations = android.R.style.Animation_Translucent;
- params.gravity = Gravity.START | Gravity.TOP;
- params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
-// params.y = (int) (mPosition.getPercentageY() * getMaxWindowY());
- final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
- params.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
- updateAccessibilityTitle(params);
- return params;
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mLastConfiguration.setTo(newConfig);
-
- final int diff = newConfig.diff(mLastConfiguration);
- if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
- updateAccessibilityTitle(mCurrentLayoutParams);
- }
-
- updateDimensions();
- updateListViewWith(newConfig);
- updateItemViewWith(mSizeType);
- updateColor();
- updateStrokeWith(newConfig.uiMode, mAlignment);
- updateLocationWith(mPosition);
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- setSystemGestureExclusion();
- }
-
- @VisibleForTesting
- void snapToLocation(int endX, int endY) {
- mDragAnimator.cancel();
- mDragAnimator.removeAllUpdateListeners();
- mDragAnimator.addUpdateListener(anim -> onDragAnimationUpdate(anim, endX, endY));
- mDragAnimator.start();
- }
-
- private void onDragAnimationUpdate(ValueAnimator animator, int endX, int endY) {
- float value = (float) animator.getAnimatedValue();
- final int newX = (int) (((1 - value) * mCurrentLayoutParams.x) + (value * endX));
- final int newY = (int) (((1 - value) * mCurrentLayoutParams.y) + (value * endY));
-
- mCurrentLayoutParams.x = newX;
- mCurrentLayoutParams.y = newY;
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- private int getMinWindowX() {
- return -getMarginStartEndWith(mLastConfiguration);
- }
-
- private int getMaxWindowX() {
- return mDisplayWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
- }
-
- private int getMaxWindowY() {
- return mDisplayHeight - getWindowHeight();
- }
-
- private InstantInsetLayerDrawable getMenuLayerDrawable() {
- return (InstantInsetLayerDrawable) mListView.getBackground();
- }
-
- private GradientDrawable getMenuGradientDrawable() {
- return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
- }
-
- private Insets getDisplayInsets(WindowMetrics metrics) {
- return metrics.getWindowInsets().getInsetsIgnoringVisibility(
- systemBars() | displayCutout());
- }
-
- /**
- * Updates the floating menu to be fixed at the side of the display.
- */
- private void updateLocationWith(Position position) {
- final @Alignment int alignment = transformToAlignment(position.getPercentageX());
- mCurrentLayoutParams.x = (alignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
- final int currentLayoutY = (int) (position.getPercentageY() * getMaxWindowY());
- mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- /**
- * Gets the moving interval to not overlap between the keyboard and menu view.
- *
- * @return the moving interval if they overlap each other, otherwise 0.
- */
- private int getInterval() {
- final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
- final int imeY = mDisplayHeight - mImeInsetsRect.bottom;
- final int layoutBottomY = currentLayoutY + getWindowHeight();
-
- return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
- }
-
- private void updateMarginWith(Configuration configuration) {
- // Avoid overlapping with system bars under landscape mode, update the margins of the menu
- // to align the edge of system bars.
- final int marginStartEnd = getMarginStartEndWith(configuration);
- final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams();
- layoutParams.setMargins(marginStartEnd, mMargin, marginStartEnd, mMargin);
- mListView.setLayoutParams(layoutParams);
- }
-
- private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
- final float halfWidth = getLayoutWidth() / 2.0f;
- final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;
- mListView.animate().translationX(side == Alignment.RIGHT ? offset : -offset);
- }
-
- private void updateScrollModeWith(boolean hasExceededMaxLayoutHeight) {
- mListView.setOverScrollMode(hasExceededMaxLayoutHeight
- ? OVER_SCROLL_ALWAYS
- : OVER_SCROLL_NEVER);
- }
-
- private void updateColor() {
- final int menuColorResId = R.color.accessibility_floating_menu_background;
- getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId));
- }
-
- private void updateStrokeWith(int uiMode, @Alignment int side) {
- updateInsetWith(uiMode, side);
-
- final boolean isNightMode =
- (uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
- final Resources res = getResources();
- final int width =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width);
- final int strokeWidth = isNightMode ? width : 0;
- final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark);
- getMenuGradientDrawable().setStroke(strokeWidth, strokeColor);
- }
-
- private void updateRadiusWith(@SizeType int sizeType, @RadiusType int radiusType,
- int itemCount) {
- mRadius =
- getResources().getDimensionPixelSize(getRadiusResId(sizeType, itemCount));
- setRadius(mRadius, radiusType);
- }
-
- private void updateInsetWith(int uiMode, @Alignment int side) {
- final boolean isNightMode =
- (uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
-
- final int layerInset = isNightMode ? mInset : 0;
- final int insetLeft = (side == Alignment.LEFT) ? layerInset : 0;
- final int insetRight = (side == Alignment.RIGHT) ? layerInset : 0;
- setInset(insetLeft, insetRight);
- }
-
- private void updateAccessibilityTitle(WindowManager.LayoutParams params) {
- params.accessibilityTitle = getResources().getString(
- com.android.internal.R.string.accessibility_select_shortcut_menu_title);
- }
-
- private void setInset(int left, int right) {
- final LayerDrawable layerDrawable = getMenuLayerDrawable();
- if (layerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM) == left
- && layerDrawable.getLayerInsetRight(INDEX_MENU_ITEM) == right) {
- return;
- }
-
- layerDrawable.setLayerInset(INDEX_MENU_ITEM, left, 0, right, 0);
- }
-
- @VisibleForTesting
- boolean hasExceededMaxLayoutHeight() {
- return calculateActualLayoutHeight() > getMaxLayoutHeight();
- }
-
- @Alignment
- private int transformToAlignment(@FloatRange(from = 0.0, to = 1.0) float percentageX) {
- return (percentageX < 0.5f) ? Alignment.LEFT : Alignment.RIGHT;
- }
-
- private float transformCurrentPercentageXToEdge() {
- final float percentageX = calculateCurrentPercentageX();
- return (percentageX < 0.5) ? 0.0f : 1.0f;
- }
-
- private float calculateCurrentPercentageX() {
- return mCurrentLayoutParams.x / (float) getMaxWindowX();
- }
-
- private float calculateCurrentPercentageY() {
- return mCurrentLayoutParams.y / (float) getMaxWindowY();
- }
-
- private int calculateActualLayoutHeight() {
- return (mPadding + mIconHeight) * mTargets.size() + mPadding;
- }
-
- private int getMarginStartEndWith(Configuration configuration) {
- return configuration != null
- && configuration.orientation == ORIENTATION_PORTRAIT
- ? mMargin : 0;
- }
-
- private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
- return sizeType == SizeType.SMALL
- ? getSmallSizeResIdWith(itemCount)
- : getLargeSizeResIdWith(itemCount);
- }
-
- private int getSmallSizeResIdWith(int itemCount) {
- return itemCount > 1
- ? R.dimen.accessibility_floating_menu_small_multiple_radius
- : R.dimen.accessibility_floating_menu_small_single_radius;
- }
-
- private int getLargeSizeResIdWith(int itemCount) {
- return itemCount > 1
- ? R.dimen.accessibility_floating_menu_large_multiple_radius
- : R.dimen.accessibility_floating_menu_large_single_radius;
- }
-
- @VisibleForTesting
- Rect getAvailableBounds() {
- return new Rect(0, 0, mDisplayWidth - getWindowWidth(),
- mDisplayHeight - getWindowHeight());
- }
-
- private int getMaxLayoutHeight() {
- return mDisplayHeight - mMargin * 2;
- }
-
- private int getLayoutWidth() {
- return mPadding * 2 + mIconWidth;
- }
-
- private int getLayoutHeight() {
- return Math.min(getMaxLayoutHeight(), calculateActualLayoutHeight());
- }
-
- private int getWindowWidth() {
- return getMarginStartEndWith(mLastConfiguration) * 2 + getLayoutWidth();
- }
-
- private int getWindowHeight() {
- return Math.min(mDisplayHeight, mMargin * 2 + getLayoutHeight());
- }
-
- private void setSystemGestureExclusion() {
- final Rect excludeZone =
- new Rect(0, 0, getWindowWidth(), getWindowHeight());
- post(() -> setSystemGestureExclusionRects(
- mIsShowing
- ? Collections.singletonList(excludeZone)
- : Collections.emptyList()));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
deleted file mode 100644
index 61fc7ed..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.util.TypedValue.COMPLEX_UNIT_PX;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.UNSPECIFIED;
-
-import android.annotation.UiContext;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Bundle;
-import android.text.method.MovementMethod;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
-
-/**
- * Base tooltip view that shows the information about the operation of the
- * Accessibility floating menu. In addition, the anchor view is only for {@link
- * AccessibilityFloatingMenuView}, it should be more suited for displaying one-off menus to avoid
- * the performance hit for the extra window.
- */
-class BaseTooltipView extends FrameLayout {
- private int mFontSize;
- private int mTextViewMargin;
- private int mTextViewPadding;
- private int mTextViewCornerRadius;
- private int mArrowMargin;
- private int mArrowWidth;
- private int mArrowHeight;
- private int mArrowCornerRadius;
- private int mScreenWidth;
- private boolean mIsShowing;
- private TextView mTextView;
- private final WindowManager.LayoutParams mCurrentLayoutParams;
- private final WindowManager mWindowManager;
- private final AccessibilityFloatingMenuView mAnchorView;
-
- BaseTooltipView(@UiContext Context context, AccessibilityFloatingMenuView anchorView) {
- super(context);
- mWindowManager = context.getSystemService(WindowManager.class);
- mAnchorView = anchorView;
- mCurrentLayoutParams = createDefaultLayoutParams();
-
- initViews();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- mAnchorView.onConfigurationChanged(newConfig);
- updateTooltipView();
-
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- hide();
- }
-
- return super.onTouchEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.addAction(AccessibilityAction.ACTION_DISMISS);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (action == AccessibilityAction.ACTION_DISMISS.getId()) {
- hide();
- return true;
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
-
- void show() {
- if (isShowing()) {
- return;
- }
-
- mIsShowing = true;
- updateTooltipView();
-
- mWindowManager.addView(this, mCurrentLayoutParams);
- }
-
- void hide() {
- if (!isShowing()) {
- return;
- }
-
- mIsShowing = false;
- mWindowManager.removeView(this);
- }
-
- void setDescription(CharSequence text) {
- mTextView.setText(text);
- }
-
- void setMovementMethod(MovementMethod movement) {
- mTextView.setMovementMethod(movement);
- }
-
- private boolean isShowing() {
- return mIsShowing;
- }
-
- private void initViews() {
- final View contentView =
- LayoutInflater.from(getContext()).inflate(
- R.layout.accessibility_floating_menu_tooltip, this, false);
-
- mTextView = contentView.findViewById(R.id.text);
-
- addView(contentView);
- }
-
- private static WindowManager.LayoutParams createDefaultLayoutParams() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
- PixelFormat.TRANSLUCENT);
- params.windowAnimations = android.R.style.Animation_Translucent;
- params.gravity = Gravity.START | Gravity.TOP;
-
- return params;
- }
-
- private void updateDimensions() {
- final Resources res = getResources();
- final DisplayMetrics dm = res.getDisplayMetrics();
- mScreenWidth = dm.widthPixels;
- mArrowWidth =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
- mArrowHeight =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height);
- mArrowMargin =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_arrow_margin);
- mArrowCornerRadius =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_arrow_corner_radius);
- mFontSize =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size);
- mTextViewMargin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin);
- mTextViewPadding =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding);
- mTextViewCornerRadius =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_text_corner_radius);
- }
-
- private void updateTooltipView() {
- updateDimensions();
- updateTextView();
-
- final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen();
- updateArrowWith(anchorViewLocation);
- updateWidthWith(anchorViewLocation);
- updateLocationWith(anchorViewLocation);
- }
-
- private void updateTextView() {
- mTextView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
- mTextView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
- mTextViewPadding);
-
- final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground();
- gradientDrawable.setCornerRadius(mTextViewCornerRadius);
- gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.colorAccentPrimary));
- }
-
- private void updateArrowWith(Rect anchorViewLocation) {
- final boolean isAnchorViewOnLeft = isAnchorViewOnLeft(anchorViewLocation);
- final View arrowView = findViewById(isAnchorViewOnLeft
- ? R.id.arrow_left
- : R.id.arrow_right);
- arrowView.setVisibility(VISIBLE);
- drawArrow(arrowView, isAnchorViewOnLeft);
-
- final LinearLayout.LayoutParams layoutParams =
- (LinearLayout.LayoutParams) arrowView.getLayoutParams();
- layoutParams.width = mArrowWidth;
- layoutParams.height = mArrowHeight;
-
- final int leftMargin = isAnchorViewOnLeft ? 0 : mArrowMargin;
- final int rightMargin = isAnchorViewOnLeft ? mArrowMargin : 0;
- layoutParams.setMargins(leftMargin, 0, rightMargin, 0);
- arrowView.setLayoutParams(layoutParams);
- }
-
- private void updateWidthWith(Rect anchorViewLocation) {
- final ViewGroup.LayoutParams layoutParams = mTextView.getLayoutParams();
- layoutParams.width = getTextWidthWith(anchorViewLocation);
- mTextView.setLayoutParams(layoutParams);
- }
-
- private void updateLocationWith(Rect anchorViewLocation) {
- mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation)
- ? anchorViewLocation.width()
- : mScreenWidth - getWindowWidthWith(anchorViewLocation)
- - anchorViewLocation.width();
- mCurrentLayoutParams.y =
- anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2);
- }
-
- private void drawArrow(View view, boolean isPointingLeft) {
- final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
- final TriangleShape triangleShape =
- TriangleShape.createHorizontal(layoutParams.width, layoutParams.height,
- isPointingLeft);
- final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
- final Paint arrowPaint = arrowDrawable.getPaint();
- arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.colorAccentPrimary));
- final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
- arrowPaint.setPathEffect(effect);
- view.setBackground(arrowDrawable);
- }
-
- private boolean isAnchorViewOnLeft(Rect anchorViewLocation) {
- return anchorViewLocation.left < (mScreenWidth / 2);
- }
-
- private int getTextWidthWith(Rect anchorViewLocation) {
- final int widthSpec =
- MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
- final int heightSpec =
- MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
- mTextView.measure(widthSpec, heightSpec);
- return mTextView.getMeasuredWidth();
- }
-
- private int getTextHeightWith(Rect anchorViewLocation) {
- final int widthSpec =
- MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
- final int heightSpec =
- MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
- mTextView.measure(widthSpec, heightSpec);
- return mTextView.getMeasuredHeight();
- }
-
- private int getAvailableTextWidthWith(Rect anchorViewLocation) {
- return mScreenWidth - anchorViewLocation.width() - mArrowWidth - mArrowMargin
- - mTextViewMargin;
- }
-
- private int getWindowWidthWith(Rect anchorViewLocation) {
- return getTextWidthWith(anchorViewLocation) + mArrowWidth + mArrowMargin;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java
deleted file mode 100644
index 49056a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import android.content.Context;
-
-import com.android.systemui.R;
-
-/**
- * Dock tooltip view that shows the info about moving the Accessibility button to the edge to hide.
- */
-class DockTooltipView extends BaseTooltipView {
- private final AccessibilityFloatingMenuView mAnchorView;
-
- DockTooltipView(Context context, AccessibilityFloatingMenuView anchorView) {
- super(context, anchorView);
- mAnchorView = anchorView;
-
- setDescription(
- getContext().getText(R.string.accessibility_floating_button_docking_tooltip));
- }
-
- @Override
- void hide() {
- super.hide();
-
- mAnchorView.stopTranslateXAnimation();
- }
-
- @Override
- void show() {
- super.show();
-
- mAnchorView.startTranslateXAnimation();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
deleted file mode 100644
index 93b0676..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-
-import com.android.systemui.R;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
-
-import java.lang.ref.WeakReference;
-
-/**
- * An accessibility item delegate for the individual items of the list view
- * {@link AccessibilityFloatingMenuView}.
- */
-final class ItemDelegateCompat extends RecyclerViewAccessibilityDelegate.ItemDelegate {
- private final WeakReference<AccessibilityFloatingMenuView> mMenuViewRef;
-
- ItemDelegateCompat(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
- AccessibilityFloatingMenuView menuView) {
- super(recyclerViewDelegate);
- this.mMenuViewRef = new WeakReference<>(menuView);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
-
- if (mMenuViewRef.get() == null) {
- return;
- }
- final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
-
- final Resources res = menuView.getResources();
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopLeft =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.action_move_top_left,
- res.getString(
- R.string.accessibility_floating_button_action_move_top_left));
- info.addAction(moveTopLeft);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopRight =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_top_right,
- res.getString(
- R.string.accessibility_floating_button_action_move_top_right));
- info.addAction(moveTopRight);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomLeft =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_bottom_left,
- res.getString(
- R.string.accessibility_floating_button_action_move_bottom_left));
- info.addAction(moveBottomLeft);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomRight =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_bottom_right,
- res.getString(
- R.string.accessibility_floating_button_action_move_bottom_right));
- info.addAction(moveBottomRight);
-
- final int moveEdgeId = menuView.isOvalShape()
- ? R.id.action_move_to_edge_and_hide
- : R.id.action_move_out_edge_and_show;
- final int moveEdgeTextResId = menuView.isOvalShape()
- ? R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half
- : R.string.accessibility_floating_button_action_move_out_edge_and_show;
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
- res.getString(moveEdgeTextResId));
- info.addAction(moveToOrOutEdge);
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (mMenuViewRef.get() == null) {
- return super.performAccessibilityAction(host, action, args);
- }
- final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
-
- menuView.fadeIn();
-
- final Rect bounds = menuView.getAvailableBounds();
- if (action == R.id.action_move_top_left) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.left, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_top_right) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.right, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_bottom_left) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.left, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_bottom_right) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.right, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_to_edge_and_hide) {
- menuView.setShapeType(ShapeType.HALF_OVAL);
- return true;
- }
-
- if (action == R.id.action_move_out_edge_and_show) {
- menuView.setShapeType(ShapeType.OVAL);
- return true;
- }
-
- return super.performAccessibilityAction(host, action, args);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java
deleted file mode 100644
index e4f3e31..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.text.method.LinkMovementMethod;
-
-import com.android.systemui.R;
-
-/**
- * Migration tooltip view that shows the information about the Accessibility button was replaced
- * with the floating menu.
- */
-class MigrationTooltipView extends BaseTooltipView {
- MigrationTooltipView(Context context, AccessibilityFloatingMenuView anchorView) {
- super(context, anchorView);
-
- final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
- ACCESSIBILITY_BUTTON_COMPONENT_NAME.flattenToShortString());
-
- final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
- AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
- v -> {
- getContext().startActivity(intent);
- hide();
- });
-
- final int textResId = R.string.accessibility_floating_button_migration_tooltip;
- setDescription(AnnotationLinkSpan.linkify(getContext().getText(textResId), linkInfo));
- setMovementMethod(LinkMovementMethod.getInstance());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 436f9df..1f6f6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -46,6 +46,8 @@
private var isDeviceFolded: Boolean = false
private val isSideFps: Boolean
+ private val isReverseDefaultRotation =
+ context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
set(value) {
@@ -76,7 +78,7 @@
isSideFps = sideFps
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
- if (isSideFps && displayInfo.rotation == Surface.ROTATION_180) {
+ if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
iconView.rotation = 180f
}
screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
@@ -86,7 +88,7 @@
private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
- val rotation = displayInfo.rotation
+ val rotation = getRotationFromDefault(displayInfo.rotation)
val iconAnimation = getSideFpsAnimationForTransition(rotation)
val iconViewOverlayAnimation =
getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
@@ -104,7 +106,7 @@
iconView.frame = 0
iconViewOverlay.frame = 0
- if (shouldAnimateIconViewForTransition(lastState, newState)) {
+ if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
iconView.playAnimation()
}
@@ -169,6 +171,18 @@
STATE_HELP,
STATE_ERROR -> true
STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+ STATE_AUTHENTICATED -> true
+ else -> false
+ }
+
+ private fun shouldAnimateSfpsIconViewForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ) = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> true
+ STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING ->
oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
STATE_AUTHENTICATED -> true
@@ -217,6 +231,9 @@
return if (id != null) return id else null
}
+ private fun getRotationFromDefault(rotation: Int): Int =
+ if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+
@RawRes
private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
Surface.ROTATION_90 -> if (isDeviceFolded) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 1c26841..82bb723 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,6 +21,7 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
@@ -35,6 +36,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -57,6 +59,7 @@
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
+ private final FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -65,11 +68,13 @@
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
+ mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
}
@@ -107,7 +112,11 @@
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlay.setClipData(clipData, clipSource);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ mClipboardOverlay.setClipData(clipData, clipSource);
+ } else {
+ mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
+ }
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
@@ -150,6 +159,8 @@
}
interface ClipboardOverlay {
+ void setClipDataLegacy(ClipData clipData, String clipSource);
+
void setClipData(ClipData clipData, String clipSource);
void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
new file mode 100644
index 0000000..c7aaf09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription.EXTRA_IS_SENSITIVE
+import android.content.Context
+import android.graphics.Bitmap
+import android.text.TextUtils
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import java.io.IOException
+
+data class ClipboardModel(
+ val clipData: ClipData?,
+ val source: String,
+ val type: Type = Type.OTHER,
+ val item: ClipData.Item? = null,
+ val isSensitive: Boolean = false,
+ val isRemote: Boolean = false,
+) {
+ private var _bitmap: Bitmap? = null
+
+ fun dataMatches(other: ClipboardModel?): Boolean {
+ if (other == null) {
+ return false
+ }
+ return source == other.source &&
+ type == other.type &&
+ item?.text == other.item?.text &&
+ item?.uri == other.item?.uri &&
+ isSensitive == other.isSensitive
+ }
+
+ fun loadThumbnail(context: Context): Bitmap? {
+ if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
+ try {
+ val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ _bitmap =
+ context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
+ } catch (e: IOException) {
+ Log.e(TAG, "Thumbnail loading failed!", e)
+ }
+ }
+ return _bitmap
+ }
+
+ internal companion object {
+ private val TAG: String = "ClipboardModel"
+
+ @JvmStatic
+ fun fromClipData(
+ context: Context,
+ utils: ClipboardOverlayUtils,
+ clipData: ClipData?,
+ source: String
+ ): ClipboardModel {
+ if (clipData == null || clipData.itemCount == 0) {
+ return ClipboardModel(clipData, source)
+ }
+ val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
+ val item = clipData.getItemAt(0)!!
+ val type = getType(context, item)
+ val remote = utils.isRemoteCopy(context, clipData, source)
+ return ClipboardModel(clipData, source, type, item, sensitive, remote)
+ }
+
+ private fun getType(context: Context, item: ClipData.Item): Type {
+ return if (!TextUtils.isEmpty(item.text)) {
+ Type.TEXT
+ } else if (
+ item.uri != null &&
+ context.contentResolver.getType(item.uri)?.startsWith("image") == true
+ ) {
+ Type.IMAGE
+ } else {
+ Type.OTHER
+ }
+ }
+ }
+
+ enum class Type {
+ TEXT,
+ IMAGE,
+ OTHER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 8c8ee8a..b41f308 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
package com.android.systemui.clipboardoverlay;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
@@ -31,10 +30,9 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
@@ -47,7 +45,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
@@ -55,14 +52,15 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
-import android.view.Display;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -71,7 +69,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
-import com.android.systemui.settings.DisplayTracker;
import java.io.IOException;
import java.util.Optional;
@@ -95,8 +92,6 @@
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final DisplayManager mDisplayManager;
- private final DisplayTracker mDisplayTracker;
private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final ClipboardOverlayUtils mClipboardUtils;
@@ -122,6 +117,9 @@
private Runnable mOnUiUpdate;
+ private boolean mIsMinimized;
+ private ClipboardModel mClipboardModel;
+
private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
new ClipboardOverlayView.ClipboardOverlayCallbacks() {
@Override
@@ -175,6 +173,13 @@
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
animateOut();
}
+
+ @Override
+ public void onMinimizedViewTapped() {
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ animateFromMinimized();
+ }
+ }
};
@Inject
@@ -187,33 +192,28 @@
FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
@Background Executor bgExecutor,
- UiEventLogger uiEventLogger,
- DisplayTracker displayTracker) {
+ UiEventLogger uiEventLogger) {
+ mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
- mDisplayTracker = displayTracker;
- final Context displayContext = context.createDisplayContext(getDefaultDisplay());
- mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mClipboardLogger = new ClipboardLogger(uiEventLogger);
mView = clipboardOverlayView;
mWindow = clipboardOverlayWindow;
- mWindow.init(mView::setInsets, () -> {
+ mWindow.init(this::onInsetsChanged, () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
hideImmediate();
});
+ mFeatureFlags = featureFlags;
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
- mFeatureFlags = featureFlags;
mClipboardUtils = clipboardUtils;
mBgExecutor = bgExecutor;
mView.setCallbacks(mClipboardCallbacks);
-
mWindow.withWindowAttached(() -> {
mWindow.setContentView(mView);
mView.setInsets(mWindow.getWindowInsets(),
@@ -258,8 +258,135 @@
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
+ @VisibleForTesting
+ void onInsetsChanged(WindowInsets insets, int orientation) {
+ mView.setInsets(insets, orientation);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ if (shouldShowMinimized(insets) && !mIsMinimized) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ }
+ }
+ }
+
@Override // ClipboardListener.ClipboardOverlay
- public void setClipData(ClipData clipData, String clipSource) {
+ public void setClipData(ClipData data, String source) {
+ ClipboardModel model = ClipboardModel.fromClipData(mContext, mClipboardUtils, data, source);
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ mExitAnimator.cancel();
+ }
+ boolean shouldAnimate = !model.dataMatches(mClipboardModel);
+ mClipboardModel = model;
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldAnimate) {
+ reset();
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ } else {
+ setExpandedView();
+ }
+ animateIn();
+ mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
+ } else if (!mIsMinimized) {
+ setExpandedView();
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && mClipboardModel.isRemote()) {
+ mTimeoutHandler.cancelTimeout();
+ mOnUiUpdate = null;
+ } else {
+ mOnUiUpdate = mTimeoutHandler::resetTimeout;
+ mOnUiUpdate.run();
+ }
+ }
+
+ private void setExpandedView() {
+ final ClipboardModel model = mClipboardModel;
+ mView.setMinimized(false);
+ switch (model.getType()) {
+ case TEXT:
+ if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
+ || DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (model.getItem().getTextLinks() != null) {
+ classifyText(model);
+ }
+ }
+ if (model.isSensitive()) {
+ mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+ } else {
+ mView.showTextPreview(model.getItem().getText(), false);
+ }
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
+ break;
+ case IMAGE:
+ if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
+ mView.showImagePreview(
+ model.isSensitive() ? null : model.loadThumbnail(mContext));
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = () -> editImage(model.getItem().getUri());
+ } else {
+ // image loading failed
+ mView.showDefaultTextPreview();
+ }
+ break;
+ case OTHER:
+ mView.showDefaultTextPreview();
+ break;
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)) {
+ if (!model.isRemote()) {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ } else {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ if (model.getType() != ClipboardModel.Type.OTHER) {
+ mOnShareTapped = () -> shareContent(model.getClipData());
+ mView.showShareChip();
+ }
+ }
+
+ private boolean shouldShowMinimized(WindowInsets insets) {
+ return insets.getInsets(WindowInsets.Type.ime()).bottom > 0;
+ }
+
+ private void animateFromMinimized() {
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+
+ private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
+ if (type == ClipboardModel.Type.TEXT) {
+ return mContext.getString(R.string.clipboard_text_copied);
+ } else if (type == ClipboardModel.Type.IMAGE) {
+ return mContext.getString(R.string.clipboard_image_copied);
+ } else {
+ return mContext.getString(R.string.clipboard_content_copied);
+ }
+ }
+
+ private void classifyText(ClipboardModel model) {
+ mBgExecutor.execute(() -> {
+ Optional<RemoteAction> remoteAction =
+ mClipboardUtils.getAction(model.getItem(), model.getSource());
+ if (model.equals(mClipboardModel)) {
+ remoteAction.ifPresent(action -> {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
+ mView.setActionChip(action, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ });
+ }
+ });
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipDataLegacy(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
@@ -516,10 +643,6 @@
mClipboardLogger.reset();
}
- private Display getDefaultDisplay() {
- return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
- }
-
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2d33157..c9e01ce 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -77,6 +75,8 @@
void onShareButtonTapped();
void onPreviewTapped();
+
+ void onMinimizedViewTapped();
}
private static final String TAG = "ClipboardView";
@@ -92,6 +92,7 @@
private ImageView mImagePreview;
private TextView mTextPreview;
private TextView mHiddenPreview;
+ private LinearLayout mMinimizedPreview;
private View mPreviewBorder;
private OverlayActionChip mEditChip;
private OverlayActionChip mShareChip;
@@ -117,18 +118,18 @@
@Override
protected void onFinishInflate() {
- mActionContainerBackground =
- requireNonNull(findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
- mEditChip = requireNonNull(findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
- mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+ mActionContainerBackground = requireViewById(R.id.actions_container_background);
+ mActionContainer = requireViewById(R.id.actions);
+ mClipboardPreview = requireViewById(R.id.clipboard_preview);
+ mPreviewBorder = requireViewById(R.id.preview_border);
+ mImagePreview = requireViewById(R.id.image_preview);
+ mTextPreview = requireViewById(R.id.text_preview);
+ mHiddenPreview = requireViewById(R.id.hidden_preview);
+ mMinimizedPreview = requireViewById(R.id.minimized_preview);
+ mEditChip = requireViewById(R.id.edit_chip);
+ mShareChip = requireViewById(R.id.share_chip);
+ mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
+ mDismissButton = requireViewById(R.id.dismiss_button);
mEditChip.setAlpha(1);
mShareChip.setAlpha(1);
@@ -163,6 +164,7 @@
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+ mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
}
void setEditAccessibilityAction(boolean editable) {
@@ -177,12 +179,28 @@
}
}
+ void setMinimized(boolean minimized) {
+ if (minimized) {
+ mMinimizedPreview.setVisibility(View.VISIBLE);
+ mClipboardPreview.setVisibility(View.GONE);
+ mPreviewBorder.setVisibility(View.GONE);
+ mActionContainer.setVisibility(View.GONE);
+ mActionContainerBackground.setVisibility(View.GONE);
+ } else {
+ mMinimizedPreview.setVisibility(View.GONE);
+ mClipboardPreview.setVisibility(View.VISIBLE);
+ mPreviewBorder.setVisibility(View.VISIBLE);
+ mActionContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
void setInsets(WindowInsets insets, int orientation) {
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
if (p == null) {
return;
}
Rect margins = computeMargins(insets, orientation);
+
p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
setLayoutParams(p);
requestLayout();
@@ -204,6 +222,12 @@
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
+ mMinimizedPreview.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
@@ -298,6 +322,8 @@
scaleAnim.setDuration(333);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -319,12 +345,14 @@
alphaAnim.setDuration(283);
alphaAnim.addUpdateListener(animation -> {
float alpha = animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
mActionContainer.setAlpha(alpha);
});
+ mMinimizedPreview.setAlpha(0);
mActionContainer.setAlpha(0);
mPreviewBorder.setAlpha(0);
mClipboardPreview.setAlpha(0);
@@ -356,6 +384,8 @@
scaleAnim.setDuration(250);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -377,6 +407,7 @@
alphaAnim.setDuration(166);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
@@ -399,6 +430,7 @@
mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
mHiddenPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setVisibility(View.GONE);
v.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index dbe301d..860149d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -28,12 +28,13 @@
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
+import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
import com.android.systemui.R
import java.util.Objects
-class ControlsServiceInfo(
+open class ControlsServiceInfo(
private val context: Context,
val serviceInfo: ServiceInfo
) : DefaultAppInfo(
@@ -64,7 +65,7 @@
* [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/
var panelActivity: ComponentName? = null
- private set
+ protected set
private var resolved: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index ce82af7..700f4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -123,16 +123,13 @@
userChanging = false
}
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- userChanging = true
- val newUserHandle = UserHandle.of(newUser)
- if (currentUser == newUserHandle) {
- userChanging = false
- return
- }
- setValuesForUser(newUserHandle)
+ override fun changeUser(newUser: UserHandle) {
+ userChanging = true
+ if (currentUser == newUser) {
+ userChanging = false
+ return
}
+ setValuesForUser(newUser)
}
@VisibleForTesting
@@ -233,7 +230,6 @@
dumpManager.registerDumpable(javaClass.name, this)
resetFavorites()
userChanging = false
- userTracker.addCallback(userTrackerCallback, executor)
context.registerReceiver(
restoreFinishedReceiver,
IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -245,7 +241,6 @@
}
fun destroy() {
- userTracker.removeCallback(userTrackerCallback)
context.unregisterReceiver(restoreFinishedReceiver)
listingController.removeCallback(listingCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
new file mode 100644
index 0000000..3f20c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.controls.start.ControlsStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class StartControlsStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsStartable::class)
+ abstract fun bindFeature(impl: ControlsStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
new file mode 100644
index 0000000..9d99253
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.start
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.UserHandle
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
+ *
+ * In particular, it will perform the following:
+ * * If there is no preferred selection for provider and at least one of the preferred packages
+ * provides a panel, it will select the first one that does.
+ * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
+ * displaying the panel).
+ *
+ * It will also perform those operations on user change.
+ */
+@SysUISingleton
+class ControlsStartable
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ @Background private val executor: Executor,
+ private val controlsComponent: ControlsComponent,
+ private val userTracker: UserTracker
+) : CoreStartable {
+
+ // These two controllers can only be accessed after `start` method once we've checked if the
+ // feature is enabled
+ private val controlsController: ControlsController
+ get() = controlsComponent.getControlsController().get()
+
+ private val controlsListingController: ControlsListingController
+ get() = controlsComponent.getControlsListingController().get()
+
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ controlsController.changeUser(UserHandle.of(newUser))
+ startForUser()
+ }
+ }
+
+ override fun start() {
+ if (!controlsComponent.isEnabled()) {
+ // Controls is disabled, we don't need this anymore
+ return
+ }
+ startForUser()
+ userTracker.addCallback(userTrackerCallback, executor)
+ }
+
+ private fun startForUser() {
+ selectDefaultPanelIfNecessary()
+ bindToPanel()
+ }
+
+ private fun selectDefaultPanelIfNecessary() {
+ val currentSelection = controlsController.getPreferredSelection()
+ if (currentSelection == SelectedItem.EMPTY_SELECTION) {
+ val availableServices = controlsListingController.getCurrentServices()
+ val panels = availableServices.filter { it.panelActivity != null }
+ resources
+ .getStringArray(R.array.config_controlsPreferredPackages)
+ // Looking for the first element in the string array such that there is one package
+ // that has a panel. It will return null if there are no packages in the array,
+ // or if no packages in the array have a panel associated with it.
+ .firstNotNullOfOrNull { name ->
+ panels.firstOrNull { it.componentName.packageName == name }
+ }
+ ?.let { info ->
+ controlsController.setPreferredSelection(
+ SelectedItem.PanelItem(info.loadLabel(), info.componentName)
+ )
+ }
+ }
+ }
+
+ private fun bindToPanel() {
+ val currentSelection = controlsController.getPreferredSelection()
+ val panels =
+ controlsListingController.getCurrentServices().filter { it.panelActivity != null }
+ if (
+ currentSelection is SelectedItem.PanelItem &&
+ panels.firstOrNull { it.componentName == currentSelection.componentName } != null
+ ) {
+ controlsController.bindComponentForPanel(currentSelection.componentName)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 9e71bef..58f4835 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -28,6 +28,7 @@
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import android.os.Trace
import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.util.Log
@@ -224,6 +225,7 @@
activityContext: Context
) {
Log.d(ControlsUiController.TAG, "show()")
+ Trace.instant(Trace.TRACE_TAG_APP, "ControlsUiControllerImpl#show")
this.parent = parent
this.onDismiss = onDismiss
this.activityContext = activityContext
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index f5764c2..3b6ab20 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -27,6 +27,7 @@
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
+import android.os.Trace
import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.TaskView
@@ -84,6 +85,7 @@
options,
taskView.boundsOnScreen
)
+ Trace.instant(Trace.TRACE_TAG_APP, "PanelTaskViewController - startActivity")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 48f90bc..b5a1f45 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -27,7 +27,9 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
@@ -66,7 +68,10 @@
/**
* Collection of {@link CoreStartable}s that should be run on AOSP.
*/
-@Module(includes = [MultiUserUtilsModule::class])
+@Module(includes = [
+ MultiUserUtilsModule::class,
+ StartControlsStartableModule::class
+])
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@Binds
@@ -309,4 +314,10 @@
@IntoMap
@ClassKey(DreamMonitor::class)
abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
+
+ /**Inject into AssistantAttentionMonitor */
+ @Binds
+ @IntoMap
+ @ClassKey(AssistantAttentionMonitor::class)
+ abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 976afd4..88c0c50 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -45,6 +46,7 @@
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -82,7 +84,8 @@
authController,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
)
}
@@ -104,7 +107,8 @@
private val authController: AuthController,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val mainExecutor: Executor
+ private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -136,7 +140,8 @@
alignedBound,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
view.id = viewId
view.setColor(tintColor)
@@ -155,8 +160,9 @@
layoutParams.let { lp ->
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+ logger.faceSensorLocation(authController.faceSensorLocation)
authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
- val faceScanningHeight = (faceAuthSensorHeight * 2).toInt()
+ val faceScanningHeight = (faceAuthSensorHeight * 2)
when (rotation) {
Surface.ROTATION_0, Surface.ROTATION_180 ->
lp.height = faceScanningHeight
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
new file mode 100644
index 0000000..49d7f78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.dreams;
+
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.callbacks.AssistantAttentionCallback;
+import com.android.systemui.dreams.conditions.AssistantAttentionCondition;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link CoreStartable} to retain a monitor for tracking assistant attention.
+ */
+public class AssistantAttentionMonitor implements CoreStartable {
+ private static final String TAG = "AssistAttentionMonitor";
+
+ // We retain a reference to the monitor so it is not garbage-collected.
+ private final Monitor mConditionMonitor;
+ private final AssistantAttentionCondition mAssistantAttentionCondition;
+ private final AssistantAttentionCallback mCallback;
+
+ @Inject
+ public AssistantAttentionMonitor(
+ Monitor monitor,
+ AssistantAttentionCondition assistantAttentionCondition,
+ AssistantAttentionCallback callback) {
+ mConditionMonitor = monitor;
+ mAssistantAttentionCondition = assistantAttentionCondition;
+ mCallback = callback;
+
+ }
+ @Override
+ public void start() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "started");
+ }
+
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(mAssistantAttentionCondition)
+ .build());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 2c7ecb1..b7f6a70 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -56,6 +56,8 @@
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
+ public static final int STATE_HAS_ASSISTANT_ATTENTION = 1 << 4;
+ public static final int STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE = 1 << 5;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -251,6 +253,22 @@
return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
+ /**
+ * Returns whether assistant currently has the user's attention.
+ * @return {@code true} if assistant has the user's attention, {@code false} otherwise.
+ */
+ public boolean hasAssistantAttention() {
+ return containsState(STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Returns whether the dream overlay status bar is currently visible.
+ * @return {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public boolean isDreamOverlayStatusBarVisible() {
+ return containsState(STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -310,6 +328,23 @@
}
/**
+ * Sets whether assistant currently has the user's attention.
+ * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
+ */
+ public void setHasAssistantAttention(boolean hasAttention) {
+ modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Sets whether the dream overlay status bar is visible.
+ * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public void setDreamOverlayStatusBarVisible(boolean visible) {
+ modifyState(
+ visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 96bce4c..7c1bfed 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -52,7 +52,8 @@
STATUS_ICON_CAMERA_DISABLED,
STATUS_ICON_MIC_DISABLED,
STATUS_ICON_MIC_CAMERA_DISABLED,
- STATUS_ICON_PRIORITY_MODE_ON
+ STATUS_ICON_PRIORITY_MODE_ON,
+ STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
})
public @interface StatusIconType {}
public static final int STATUS_ICON_NOTIFICATIONS = 0;
@@ -62,6 +63,7 @@
public static final int STATUS_ICON_MIC_DISABLED = 4;
public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5;
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
+ public static final int STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE = 7;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
private Context mContext;
@@ -132,6 +134,8 @@
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode)));
+ mStatusIcons.put(STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+ fetchStatusIconForResId(R.id.dream_overlay_assistant_attention_indicator));
mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status);
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 90c440c..2221a04 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -113,6 +113,7 @@
mEntryAnimationsFinished =
mDreamOverlayStateController.areEntryAnimationsFinished();
updateVisibility();
+ updateAssistantAttentionIcon();
}
};
@@ -214,6 +215,7 @@
provider -> provider.removeCallback(mNotificationCountCallback));
mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mView.removeAllExtraStatusBarItemViews();
+ mDreamOverlayStateController.setDreamOverlayStatusBarVisible(false);
mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
mTouchInsetSession.clear();
@@ -270,12 +272,20 @@
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
+ private void updateAssistantAttentionIcon() {
+ showIcon(DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+ mDreamOverlayStateController.hasAssistantAttention());
+ }
+
private void updateVisibility() {
- if (shouldShowStatusBar()) {
- mView.setVisibility(View.VISIBLE);
- } else {
- mView.setVisibility(View.INVISIBLE);
+ final int currentVisibility = mView.getVisibility();
+ final int newVisibility = shouldShowStatusBar() ? View.VISIBLE : View.INVISIBLE;
+ if (currentVisibility == newVisibility) {
+ return;
}
+
+ mView.setVisibility(newVisibility);
+ mDreamOverlayStateController.setDreamOverlayStatusBarVisible(newVisibility == View.VISIBLE);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java
new file mode 100644
index 0000000..5d8a9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dreams.callbacks;
+
+import android.util.Log;
+
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A callback that informs {@link DreamOverlayStateController} when assistant has the user's
+ * attention.
+ */
+public class AssistantAttentionCallback implements Monitor.Callback {
+ private static final String TAG = "AssistAttentionCallback";
+
+ private final DreamOverlayStateController mStateController;
+
+ @Inject
+ public AssistantAttentionCallback(DreamOverlayStateController stateController) {
+ mStateController = stateController;
+ }
+
+ @Override
+ public void onConditionsChanged(boolean allConditionsMet) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConditionChanged:" + allConditionsMet);
+ }
+
+ mStateController.setHasAssistantAttention(allConditionsMet);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
new file mode 100644
index 0000000..250cfec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dreams.conditions;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
+ */
+public class AssistantAttentionCondition extends Condition {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final AssistUtils mAssistUtils;
+ private boolean mEnabled;
+
+ private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
+ new IVisualQueryDetectionAttentionListener.Stub() {
+ @Override
+ public void onAttentionGained() {
+ updateCondition(true);
+ }
+
+ @Override
+ public void onAttentionLost() {
+ updateCondition(false);
+ }
+ };
+
+ private final DreamOverlayStateController.Callback mCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (mDreamOverlayStateController.isDreamOverlayStatusBarVisible()) {
+ enableVisualQueryDetection();
+ } else {
+ disableVisualQueryDetection();
+ }
+ }
+ };
+
+ @Inject
+ public AssistantAttentionCondition(
+ DreamOverlayStateController dreamOverlayStateController,
+ AssistUtils assistUtils) {
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mAssistUtils = assistUtils;
+ }
+
+ @Override
+ protected void start() {
+ mDreamOverlayStateController.addCallback(mCallback);
+ }
+
+ @Override
+ protected void stop() {
+ disableVisualQueryDetection();
+ mDreamOverlayStateController.removeCallback(mCallback);
+ }
+
+ private void enableVisualQueryDetection() {
+ if (mEnabled) {
+ return;
+ }
+ mEnabled = true;
+ mAssistUtils.enableVisualQueryDetection(mVisualQueryDetectionAttentionListener);
+ }
+
+ private void disableVisualQueryDetection() {
+ if (!mEnabled) {
+ return;
+ }
+ mEnabled = false;
+ mAssistUtils.disableVisualQueryDetection();
+ // Make sure the condition is set to false as well.
+ updateCondition(false);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index cd911c1..94101df 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -125,8 +125,7 @@
resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
// TODO(b/254512676): Tracking Bug
- @JvmField
- val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
+ @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks")
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
@@ -235,7 +234,7 @@
// TODO(b/258517050): Clean up after the feature is launched.
@JvmField
val SMARTSPACE_DATE_WEATHER_DECOUPLED =
- sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false)
+ sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = true)
// 500 - quick settings
@@ -266,11 +265,10 @@
// 600- status bar
// TODO(b/256614753): Tracking Bug
- val NEW_STATUS_BAR_MOBILE_ICONS =
- unreleasedFlag(606, "new_status_bar_mobile_icons", teamfood = true)
+ val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
// TODO(b/256614210): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon", teamfood = true)
+ val NEW_STATUS_BAR_WIFI_ICON = releasedFlag(607, "new_status_bar_wifi_icon")
// TODO(b/256614751): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
@@ -311,9 +309,7 @@
val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
// 804 - monochromatic themes
- @JvmField
- val MONOCHROMATIC_THEMES =
- sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
+ @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
// 900 - media
// TODO(b/254512697): Tracking Bug
@@ -335,12 +331,12 @@
@JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
// TODO(b/254513168): Tracking Bug
- @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+ @JvmField val UMO_SURFACE_RIPPLE = releasedFlag(907, "umo_surface_ripple")
@JvmField val MEDIA_FALSING_PENALTY = releasedFlag(908, "media_falsing_media")
// TODO(b/261734857): Tracking Bug
- @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
+ @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise")
// TODO(b/263272731): Tracking Bug
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
@@ -354,14 +350,17 @@
unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
// TODO(b/266157412): Tracking Bug
- val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+ val MEDIA_RETAIN_SESSIONS = releasedFlag(913, "media_retain_sessions")
// TODO(b/266739309): Tracking Bug
@JvmField
- val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+ val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
// TODO(b/267007629): Tracking Bug
- val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
+ val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
+
+ // TODO(b/267166152) : Tracking Bug
+ val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -538,14 +537,10 @@
// TODO(b/266983474) Tracking Bug
val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
- // 1600 - accessibility
- // TODO(b/262224538): Tracking Bug
- @JvmField
- val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
- releasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
-
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+ // TODO(b/267162944): Tracking bug
+ @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
@@ -565,7 +560,7 @@
@JvmField
val CONTROLS_MANAGEMENT_NEW_FLOWS =
- unreleasedFlag(2002, "controls_management_new_flows", teamfood = true)
+ releasedFlag(2002, "controls_management_new_flows", teamfood = true)
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index d085db9..da91572 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -27,16 +27,24 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@SysUISingleton
@@ -46,6 +54,9 @@
private val userFileManager: UserFileManager,
private val ringerModeTracker: RingerModeTracker,
private val audioManager: AudioManager,
+ @Application private val coroutineScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
@@ -58,7 +69,7 @@
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
ringerModeTracker.ringerModeInternal.asFlow()
- .onStart { emit(getLastNonSilentRingerMode()) }
+ .onStart { getLastNonSilentRingerMode() }
.distinctUntilChanged()
.onEach { mode ->
// only remember last non-SILENT ringer mode
@@ -87,54 +98,60 @@
activationState,
)
}
+ .flowOn(backgroundDispatcher)
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- val newRingerMode: Int
- val currentRingerMode =
- ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
- if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
- newRingerMode = previousNonSilentMode
- } else {
- previousNonSilentMode = currentRingerMode
- newRingerMode = AudioManager.RINGER_MODE_SILENT
- }
+ coroutineScope.launch(backgroundDispatcher) {
+ val newRingerMode: Int
+ val currentRingerMode = audioManager.ringerModeInternal
+ if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ newRingerMode = previousNonSilentMode
+ } else {
+ previousNonSilentMode = currentRingerMode
+ newRingerMode = AudioManager.RINGER_MODE_SILENT
+ }
- if (currentRingerMode != newRingerMode) {
- audioManager.ringerModeInternal = newRingerMode
+ if (currentRingerMode != newRingerMode) {
+ audioManager.ringerModeInternal = newRingerMode
+ }
}
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
- if (audioManager.isVolumeFixed) {
- KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- } else {
- KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ withContext(backgroundDispatcher) {
+ if (audioManager.isVolumeFixed) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ }
}
/**
* Gets the last non-silent ringer mode from shared-preferences if it exists. This is
* cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
*/
- private fun getLastNonSilentRingerMode(): Int =
- userFileManager.getSharedPreferences(
- MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId
- ).getInt(
- LAST_NON_SILENT_RINGER_MODE_KEY,
- ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
- )
+ private suspend fun getLastNonSilentRingerMode(): Int =
+ withContext(backgroundDispatcher) {
+ userFileManager.getSharedPreferences(
+ MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ ).getInt(
+ LAST_NON_SILENT_RINGER_MODE_KEY,
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ )
+ }
private fun <T> LiveData<T>.asFlow(): Flow<T?> =
- conflatedCallbackFlow {
- val observer = Observer { value: T -> trySend(value) }
- observeForever(observer)
- send(value)
- awaitClose { removeObserver(observer) }
- }
+ conflatedCallbackFlow {
+ val observer = Observer { value: T -> trySend(value) }
+ observeForever(observer)
+ send(value)
+ awaitClose { removeObserver(observer) }
+ }.flowOn(mainDispatcher)
companion object {
const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index 12a6310..cd0805e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -23,15 +23,18 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import javax.inject.Inject
/**
@@ -45,6 +48,7 @@
private val userFileManager: UserFileManager,
private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : CoreStartable {
private val observer = Observer(this::updateLastNonSilentRingerMode)
@@ -72,15 +76,17 @@
}
private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
- if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
- userFileManager.getSharedPreferences(
- MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId
- )
- .edit()
- .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
- .apply()
+ coroutineScope.launch(backgroundDispatcher) {
+ if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+ userFileManager.getSharedPreferences(
+ MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ .edit()
+ .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+ .apply()
+ }
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index de15890..0af596a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -33,7 +33,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -48,12 +47,13 @@
import kotlinx.coroutines.flow.transformLatest
/**
- * Acts as source of truth for biometric features.
+ * Acts as source of truth for biometric authentication related settings like enrollments, device
+ * policy, etc.
*
* Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
* upstream changes.
*/
-interface BiometricRepository {
+interface BiometricSettingsRepository {
/** Whether any fingerprints are enrolled for the current user. */
val isFingerprintEnrolled: StateFlow<Boolean>
@@ -68,9 +68,8 @@
val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
}
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
-class BiometricRepositoryImpl
+class BiometricSettingsRepositoryImpl
@Inject
constructor(
context: Context,
@@ -82,7 +81,7 @@
@Application scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
@Main looper: Looper,
-) : BiometricRepository {
+) : BiometricSettingsRepository {
/** UserId of the current selected user. */
private val selectedUserId: Flow<Int> =
@@ -90,7 +89,7 @@
override val isFingerprintEnrolled: StateFlow<Boolean> =
selectedUserId
- .flatMapLatest { userId ->
+ .flatMapLatest {
conflatedCallbackFlow {
val callback =
object : AuthController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2cbc2bb..4a262f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -31,7 +31,10 @@
@Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
- @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
+ @Binds
+ fun biometricSettingsRepository(
+ impl: BiometricSettingsRepositoryImpl
+ ): BiometricSettingsRepository
@Binds
fun deviceEntryFingerprintAuthRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index 6020ef8..6452e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -20,7 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
@@ -34,7 +34,7 @@
@Inject
constructor(
private val bouncerRepository: KeyguardBouncerRepository,
- private val biometricRepository: BiometricRepository,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -99,9 +99,9 @@
fun canShowAlternateBouncerForFingerprint(): Boolean {
return if (isModernAlternateBouncerEnabled) {
bouncerRepository.isAlternateBouncerUIAvailable.value &&
- biometricRepository.isFingerprintEnrolled.value &&
- biometricRepository.isStrongBiometricAllowed.value &&
- biometricRepository.isFingerprintEnabledByDevicePolicy.value &&
+ biometricSettingsRepository.isFingerprintEnrolled.value &&
+ biometricSettingsRepository.isStrongBiometricAllowed.value &&
+ biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
!deviceEntryFingerprintAuthRepository.isLockedOut.value
} else {
legacyAlternateBouncer != null &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 57c3b31..fb0fb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -154,7 +154,11 @@
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -203,7 +207,11 @@
}
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
@@ -367,6 +375,10 @@
name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW,
value = featureFlags.isEnabled(Flags.WALLPAPER_FULLSCREEN_PREVIEW),
),
+ KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
+ value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+ )
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 8808574..adde595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -177,7 +177,8 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- clockController.clock?.events?.onTimeTick()
+ clockController.clock?.smallClock?.events?.onTimeTick()
+ clockController.clock?.largeClock?.events?.onTimeTick()
}
}
broadcastDispatcher.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
new file mode 100644
index 0000000..5acaa46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.log
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.core.graphics.toRectF
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.ScreenDecorationsLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import javax.inject.Inject
+
+private const val TAG = "ScreenDecorationsLog"
+
+/**
+ * Helper class for logging for [com.android.systemui.ScreenDecorations]
+ *
+ * To enable logcat echoing for an entire buffer:
+ *
+ * ```
+ * adb shell settings put global systemui/buffer/ScreenDecorationsLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class ScreenDecorationsLogger
+@Inject
+constructor(
+ @ScreenDecorationsLog private val logBuffer: LogBuffer,
+) {
+ fun cameraProtectionBoundsForScanningOverlay(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Face scanning overlay present camera protection bounds: $str1" }
+ )
+ }
+
+ fun hwcLayerCameraProtectionBounds(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Hwc layer present camera protection bounds: $str1" }
+ )
+ }
+
+ fun dcvCameraBounds(id: Int, bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = bounds.toShortString()
+ int1 = id
+ },
+ { "DisplayCutoutView id=$int1 present, camera protection bounds: $str1" }
+ )
+ }
+
+ fun cutoutViewNotInitialized() {
+ logBuffer.log(TAG, ERROR, "CutoutView not initialized showCameraProtection")
+ }
+
+ fun boundingRect(boundingRectangle: RectF, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = boundingRectangle.toShortString()
+ },
+ { "Bounding rect $str1 : $str2" }
+ )
+ }
+
+ fun boundingRect(boundingRectangle: Rect, context: String) {
+ boundingRect(boundingRectangle.toRectF(), context)
+ }
+
+ fun onMeasureDimensions(
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
+ measuredWidth: Int,
+ measuredHeight: Int
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ long1 = widthMeasureSpec.toLong()
+ long2 = heightMeasureSpec.toLong()
+ int1 = measuredWidth
+ int2 = measuredHeight
+ },
+ {
+ "Face scanning animation: widthMeasureSpec: $long1 measuredWidth: $int1, " +
+ "heightMeasureSpec: $long2 measuredHeight: $int2"
+ }
+ )
+ }
+
+ fun faceSensorLocation(faceSensorLocation: Point?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = faceSensorLocation?.y?.times(2) ?: 0
+ str1 = "$faceSensorLocation"
+ },
+ { "Reinflating view: Face sensor location: $str1, faceScanningHeight: $int1" }
+ )
+ }
+
+ fun onSensorLocationChanged() {
+ logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f61d135..c02fd9c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -350,6 +350,16 @@
}
/**
+ * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
+ */
+ @Provides
+ @SysUISingleton
+ @ScreenDecorationsLog
+ public static LogBuffer provideScreenDecorationsLog(LogBufferFactory factory) {
+ return factory.create("ScreenDecorationsLog", 200);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
new file mode 100644
index 0000000..de2a8b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for ScreenDecorations added by SysUI. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ScreenDecorationsLog
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index a88a4ca..4f5bbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -28,11 +28,10 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.Html;
-import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.util.Slog;
import android.view.ContextThemeWrapper;
@@ -64,7 +63,10 @@
private String mAlertTitle;
private String mAlertBody;
- private String mAlertLearnMore;
+
+ private boolean mAlertLearnMoreLink;
+ private SpannableString mAlertLearnMore;
+
private AlertDialog.Builder mAlertDialog;
private AlertDialog mAlert;
private View mAlertView;
@@ -90,8 +92,20 @@
return;
}
- mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
- mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
+ mAlertBody = getString(R.string.log_access_confirmation_body);
+ mAlertLearnMoreLink = this.getResources()
+ .getBoolean(R.bool.log_access_confirmation_learn_more_as_link);
+ if (mAlertLearnMoreLink) {
+ mAlertLearnMore = new SpannableString(
+ getString(R.string.log_access_confirmation_learn_more));
+ mAlertLearnMore.setSpan(new URLSpan(
+ getString(R.string.log_access_confirmation_learn_more_url)),
+ 0, mAlertLearnMore.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ mAlertLearnMore = new SpannableString(
+ getString(R.string.log_access_confirmation_learn_more_at,
+ getString(R.string.log_access_confirmation_learn_more_url)));
+ }
// create View
int themeId = R.style.LogAccessDialogTheme;
@@ -180,15 +194,6 @@
return titleString;
}
- private Spannable styleFont(String text) {
- Spannable s = (Spannable) Html.fromHtml(text);
- for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
- TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
- s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
- }
- return s;
- }
-
/**
* Returns the dialog view.
* If we cannot retrieve the package name, it returns null and we decline the full device log
@@ -207,13 +212,12 @@
.setText(mAlertTitle);
if (!TextUtils.isEmpty(mAlertLearnMore)) {
- Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
-
((TextView) view.findViewById(R.id.log_access_dialog_body))
- .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
-
- ((TextView) view.findViewById(R.id.log_access_dialog_body))
+ .setText(TextUtils.concat(mAlertBody, "\n\n", mAlertLearnMore));
+ if (mAlertLearnMoreLink) {
+ ((TextView) view.findViewById(R.id.log_access_dialog_body))
.setMovementMethod(LinkMovementMethod.getInstance());
+ }
} else {
((TextView) view.findViewById(R.id.log_access_dialog_body))
.setText(mAlertBody);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 8c1ec16..70f2dee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -20,6 +20,7 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
+import android.widget.SeekBar
import android.widget.TextView
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
@@ -37,6 +38,7 @@
// Recommendation screen
lateinit var cardIcon: ImageView
lateinit var mediaAppIcons: List<CachingIconView>
+ lateinit var mediaProgressBars: List<SeekBar>
lateinit var cardTitle: TextView
val mediaCoverContainers =
@@ -82,6 +84,13 @@
if (updatedView) {
mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
cardTitle = itemView.requireViewById(R.id.media_rec_title)
+ mediaProgressBars =
+ mediaCoverContainers.map {
+ it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+ // Media playback is in the direction of tape, not time, so it stays LTR
+ layoutDirection = View.LAYOUT_DIRECTION_LTR
+ }
+ }
} else {
cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index 1df42c6..dc7a4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -41,10 +41,12 @@
val recommendations: List<SmartspaceAction>,
/** Intent for the user's initiated dismissal. */
val dismissIntent: Intent?,
- /** The timestamp in milliseconds that headphone is connected. */
+ /** The timestamp in milliseconds that the card was generated */
val headphoneConnectionTimeMillis: Long,
/** Instance ID for [MediaUiEventLogger] */
- val instanceId: InstanceId
+ val instanceId: InstanceId,
+ /** The timestamp in milliseconds indicating when the card should be removed */
+ val expiryTimeMs: Long,
) {
/**
* Indicates if all the data is valid.
@@ -86,5 +88,12 @@
}
}
+/** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */
+const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE"
+/** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */
+const val EXTRA_VALUE_TRIGGER_HEADPHONE = "HEADPHONE_CONNECTION"
+/** Value for key [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent as a regular update */
+const val EXTRA_VALUE_TRIGGER_PERIODIC = "PERIODIC_TRIGGER"
+
const val NUM_REQUIRED_RECOMMENDATIONS = 3
private val TAG = SmartspaceMediaData::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index cf71d67..27f7b97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -66,7 +67,8 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
+ private val mediaFlags: MediaFlags,
) : MediaDataManager.Listener {
private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
internal val listeners: Set<MediaDataManager.Listener>
@@ -121,7 +123,9 @@
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- if (!data.isActive) {
+ // With persistent recommendation card, we could get a background update while inactive
+ // Otherwise, consider it an invalid update
+ if (!data.isActive && !mediaFlags.isPersistentSsCardEnabled()) {
Log.d(TAG, "Inactive recommendation data. Skip triggering.")
return
}
@@ -141,7 +145,7 @@
}
}
- val shouldReactivate = !hasActiveMedia() && hasAnyMedia()
+ val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive
if (timeSinceActive < smartspaceMaxAgeMillis) {
// It could happen there are existing active media resume cards, then we don't need to
@@ -169,7 +173,7 @@
)
}
}
- } else {
+ } else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
shouldPrioritizeMutable = true
}
@@ -252,7 +256,7 @@
if (dismissIntent == null) {
Log.w(
TAG,
- "Cannot create dismiss action click action: " + "extras missing dismiss_intent."
+ "Cannot create dismiss action click action: extras missing dismiss_intent."
)
} else if (
dismissIntent.getComponent() != null &&
@@ -264,15 +268,21 @@
} else {
broadcastSender.sendBroadcast(dismissIntent)
}
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+ } else {
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId,
+ )
+ mediaDataManager.dismissSmartspaceRecommendation(
+ smartspaceMediaData.targetId,
+ delay = 0L,
)
- mediaDataManager.dismissSmartspaceRecommendation(
- smartspaceMediaData.targetId,
- delay = 0L
- )
+ }
}
}
@@ -283,8 +293,15 @@
(smartspaceMediaData.isValid() || reactivatedKey != null))
/** Are there any media entries we should display? */
- fun hasAnyMediaOrRecommendation() =
- userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+ fun hasAnyMediaOrRecommendation(): Boolean {
+ val hasSmartspace =
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ smartspaceMediaData.isValid()
+ } else {
+ smartspaceMediaData.isActive && smartspaceMediaData.isValid()
+ }
+ return userEntries.isNotEmpty() || hasSmartspace
+ }
/** Are there any media notifications active (excluding the recommendation)? */
fun hasActiveMedia() = userEntries.any { it.value.active }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 16a2e3f..0222ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -50,7 +50,6 @@
import android.text.TextUtils
import android.util.Log
import androidx.media.utils.MediaConstants
-import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -64,6 +63,8 @@
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaDeviceData
import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
@@ -120,7 +121,6 @@
appUid = Process.INVALID_UID
)
-@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA =
SmartspaceMediaData(
targetId = "INVALID",
@@ -130,7 +130,8 @@
recommendations = emptyList(),
dismissIntent = null,
headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1)
+ instanceId = InstanceId.fakeInstanceId(-1),
+ expiryTimeMs = 0,
)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -549,6 +550,11 @@
if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
onMediaDataLoaded(key, key, it)
}
+
+ if (key == smartspaceMediaData.targetId) {
+ if (DEBUG) Log.d(TAG, "smartspace card expired")
+ dismissSmartspaceRecommendation(key, delay = 0L)
+ }
}
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
@@ -606,8 +612,8 @@
}
/**
- * Called whenever the recommendation has been expired, or swiped from QQS. This will make the
- * recommendation view to not be shown anymore during this headphone connection session.
+ * Called whenever the recommendation has been expired or removed by the user. This will remove
+ * the recommendation card entirely from the carousel.
*/
fun dismissSmartspaceRecommendation(key: String, delay: Long) {
if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
@@ -629,6 +635,23 @@
)
}
+ /** Called when the recommendation card should no longer be visible in QQS or lockscreen */
+ fun setRecommendationInactive(key: String) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) {
+ Log.e(TAG, "Only persistent recommendation can be inactive!")
+ return
+ }
+ if (DEBUG) Log.d(TAG, "Setting smartspace recommendation inactive")
+
+ if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
+ // If this doesn't match, or we've already invalidated the data, no action needed
+ return
+ }
+
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
+ }
+
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -1268,12 +1291,25 @@
if (DEBUG) {
Log.d(TAG, "Set Smartspace media to be inactive for the data update")
}
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ // Smartspace uses this signal to hide the card (e.g. when it expires or user
+ // disconnects headphones), so treat as setting inactive when flag is on
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ notifySmartspaceMediaDataLoaded(
+ smartspaceMediaData.targetId,
+ smartspaceMediaData,
)
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
+ } else {
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId,
+ )
+ notifySmartspaceMediaDataRemoved(
+ smartspaceMediaData.targetId,
+ immediately = false,
+ )
+ }
}
1 -> {
val newMediaTarget = mediaTargets.get(0)
@@ -1282,7 +1318,7 @@
return
}
if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
+ smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
else -> {
@@ -1291,7 +1327,7 @@
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId,
- false /* immediately */
+ immediately = false,
)
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
@@ -1497,21 +1533,28 @@
}
/**
- * Converts the pass-in SmartspaceTarget to SmartspaceMediaData with the pass-in active status.
+ * Converts the pass-in SmartspaceTarget to SmartspaceMediaData
*
* @return An empty SmartspaceMediaData with the valid target Id is returned if the
* SmartspaceTarget's data is invalid.
*/
- private fun toSmartspaceMediaData(
- target: SmartspaceTarget,
- isActive: Boolean
- ): SmartspaceMediaData {
+ private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
var dismissIntent: Intent? = null
if (target.baseAction != null && target.baseAction.extras != null) {
dismissIntent =
target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
as Intent?
}
+
+ val isActive =
+ when {
+ !mediaFlags.isPersistentSsCardEnabled() -> true
+ target.baseAction == null -> true
+ else ->
+ target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
+ EXTRA_VALUE_TRIGGER_PERIODIC
+ }
+
packageName(target)?.let {
return SmartspaceMediaData(
targetId = target.smartspaceTargetId,
@@ -1521,7 +1564,8 @@
recommendations = target.iconGrid,
dismissIntent = dismissIntent,
headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId()
+ instanceId = logger.getNewInstanceId(),
+ expiryTimeMs = target.expiryTimeMillis,
)
}
return EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -1529,7 +1573,8 @@
isActive = isActive,
dismissIntent = dismissIntent,
headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId()
+ instanceId = logger.getNewInstanceId(),
+ expiryTimeMs = target.expiryTimeMillis,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index a898b00..aa46b14 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -23,7 +23,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -49,10 +51,12 @@
@Main private val mainExecutor: DelayableExecutor,
private val logger: MediaTimeoutLogger,
statusBarStateController: SysuiStatusBarStateController,
- private val systemClock: SystemClock
+ private val systemClock: SystemClock,
+ private val mediaFlags: MediaFlags,
) : MediaDataManager.Listener {
private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
+ private val recommendationListeners: MutableMap<String, RecommendationListener> = mutableMapOf()
/**
* Callback representing that a media object is now expired:
@@ -93,6 +97,16 @@
listener.doTimeout()
}
}
+
+ recommendationListeners.forEach { (key, listener) ->
+ if (
+ listener.cancellation != null &&
+ listener.expiration <= systemClock.currentTimeMillis()
+ ) {
+ logger.logTimeoutCancelled(key, "Timed out while dozing")
+ listener.doTimeout()
+ }
+ }
}
}
}
@@ -155,6 +169,30 @@
mediaListeners.remove(key)?.destroy()
}
+ override fun onSmartspaceMediaDataLoaded(
+ key: String,
+ data: SmartspaceMediaData,
+ shouldPrioritize: Boolean
+ ) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) return
+
+ // First check if we already have a listener
+ recommendationListeners.get(key)?.let {
+ if (!it.destroyed) {
+ it.recommendationData = data
+ return
+ }
+ }
+
+ // Otherwise, create a new one
+ recommendationListeners[key] = RecommendationListener(key, data)
+ }
+
+ override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) return
+ recommendationListeners.remove(key)?.destroy()
+ }
+
fun isTimedOut(key: String): Boolean {
return mediaListeners[key]?.timedOut ?: false
}
@@ -335,4 +373,53 @@
}
return true
}
+
+ /** Listens to changes in recommendation card data and schedules a timeout for its expiration */
+ private inner class RecommendationListener(var key: String, data: SmartspaceMediaData) {
+ private var timedOut = false
+ var destroyed = false
+ var expiration = Long.MAX_VALUE
+ private set
+ var cancellation: Runnable? = null
+ private set
+
+ var recommendationData: SmartspaceMediaData = data
+ set(value) {
+ destroyed = false
+ field = value
+ processUpdate()
+ }
+
+ init {
+ recommendationData = data
+ }
+
+ fun destroy() {
+ cancellation?.run()
+ cancellation = null
+ destroyed = true
+ }
+
+ private fun processUpdate() {
+ if (recommendationData.expiryTimeMs != expiration) {
+ // The expiry time changed - cancel and reschedule
+ val timeout =
+ recommendationData.expiryTimeMs -
+ recommendationData.headphoneConnectionTimeMillis
+ logger.logRecommendationTimeoutScheduled(key, timeout)
+ cancellation?.run()
+ cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
+ expiration = recommendationData.expiryTimeMs
+ }
+ }
+
+ fun doTimeout() {
+ cancellation?.run()
+ cancellation = null
+ logger.logTimeout(key)
+ timedOut = true
+ expiration = Long.MAX_VALUE
+ timeoutCallback(key, timedOut)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index 8f3f054..f731dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -107,6 +107,17 @@
str1 = key
str2 = reason
},
- { "media timeout cancelled for $str1, reason: $str2" }
+ { "timeout cancelled for $str1, reason: $str2" }
+ )
+
+ fun logRecommendationTimeoutScheduled(key: String, timeout: Long) =
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ long1 = timeout
+ },
+ { "recommendation timeout scheduled for $str1 in $long1 ms" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index b2ad155..fac1d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -368,7 +368,7 @@
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- debugLogger.logRecommendationLoaded(key)
+ debugLogger.logRecommendationLoaded(key, data.isActive)
// Log the case where the hidden media carousel with the existed inactive resume
// media is shown by the Smartspace signal.
if (data.isActive) {
@@ -442,7 +442,12 @@
logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
}
} else {
- onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+ if (!mediaFlags.isPersistentSsCardEnabled()) {
+ // Handle update to inactive as a removal
+ onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+ } else {
+ addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
+ }
}
}
@@ -633,7 +638,19 @@
) =
traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- if (MediaPlayerData.getMediaPlayer(key) != null) {
+ MediaPlayerData.getMediaPlayer(key)?.let {
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ // The card exists, but could have changed active state, so update for sorting
+ MediaPlayerData.addMediaRecommendation(
+ key,
+ data,
+ it,
+ shouldPrioritize,
+ systemClock,
+ debugLogger,
+ update = true,
+ )
+ }
Log.w(TAG, "Skip adding smartspace target in carousel")
return
}
@@ -672,7 +689,7 @@
newRecs,
shouldPrioritize,
systemClock,
- debugLogger
+ debugLogger,
)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
@@ -1225,17 +1242,18 @@
player: MediaControlPanel,
shouldPrioritize: Boolean,
clock: SystemClock,
- debugLogger: MediaCarouselControllerLogger? = null
+ debugLogger: MediaCarouselControllerLogger? = null,
+ update: Boolean = false
) {
shouldPrioritizeSs = shouldPrioritize
val removedPlayer = removeMediaPlayer(key)
- if (removedPlayer != null && removedPlayer != player) {
+ if (!update && removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
}
val sortKey =
MediaSortKey(
isSsMediaRec = true,
- EMPTY.copy(isPlaying = false),
+ EMPTY.copy(active = data.isActive, isPlaying = false),
key,
clock.currentTimeMillis(),
isSsReactivated = true
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index eed1bd7..35bda15 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -48,8 +48,16 @@
fun logMediaRemoved(key: String) =
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
- fun logRecommendationLoaded(key: String) =
- buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
+ fun logRecommendationLoaded(key: String, isActive: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = isActive
+ },
+ { "add recommendation $str1, active $bool1" }
+ )
fun logRecommendationRemoved(key: String, immediately: Boolean) =
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 61cc619..1d000eb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -57,6 +57,7 @@
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -736,9 +737,14 @@
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
} else if (data != null) {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_description,
- data.getAppName(mContext));
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ contentDescription = mContext.getString(
+ R.string.controls_media_smartspace_rec_header);
+ } else {
+ contentDescription = mContext.getString(
+ R.string.controls_media_smartspace_rec_description,
+ data.getAppName(mContext));
+ }
} else {
contentDescription = null;
}
@@ -1369,6 +1375,24 @@
hasSubtitle |= !TextUtils.isEmpty(subtitle);
TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
subtitleView.setText(subtitle);
+
+ // Set up progress bar
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ SeekBar mediaProgressBar =
+ mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+ TextView mediaSubtitle =
+ mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+ // show progress bar if the recommended album is played.
+ Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+ if (progress == null || progress <= 0.0) {
+ mediaProgressBar.setVisibility(View.GONE);
+ mediaSubtitle.setVisibility(View.VISIBLE);
+ } else {
+ mediaProgressBar.setProgress((int) (progress * 100));
+ mediaProgressBar.setVisibility(View.VISIBLE);
+ mediaSubtitle.setVisibility(View.GONE);
+ }
+ }
}
mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1443,6 +1467,12 @@
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ mRecommendationViewHolder.getMediaProgressBars().forEach(
+ (progressBar) -> progressBar.setProgressTintList(
+ ColorStateList.valueOf(textPrimaryColor))
+ );
+ }
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index a689dc3..c3fa76e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -58,4 +58,7 @@
/** Check whether to get progress information for resume players */
fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+
+ /** If true, do not automatically dismiss the recommendation card */
+ fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 1f27582..537dbb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -136,9 +136,9 @@
),
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_START_CAST ||
- nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -158,9 +158,9 @@
),
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_END_CAST ||
- nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -173,9 +173,9 @@
endItem = SenderEndItem.Error,
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_START_CAST ||
- nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -188,9 +188,9 @@
endItem = SenderEndItem.Error,
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_END_CAST ||
- nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -210,9 +210,9 @@
}
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState.transferStatus == TransferStatus.NOT_STARTED ||
- nextState.transferStatus == TransferStatus.IN_PROGRESS
+ // When far away, we can go to any state that represents the start of a transfer
+ // sequence.
+ return stateIsStartOfSequence(nextState)
}
};
@@ -227,6 +227,20 @@
return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
+ /**
+ * Returns true if moving from this state to [nextState] is a valid transition.
+ *
+ * In general, we expect a media transfer go to through a sequence of states:
+ * For push-to-receiver:
+ * - ALMOST_CLOSE_TO_START_CAST => TRANSFER_TO_RECEIVER_TRIGGERED =>
+ * TRANSFER_TO_RECEIVER_(SUCCEEDED|FAILED)
+ * - ALMOST_CLOSE_TO_END_CAST => TRANSFER_TO_THIS_DEVICE_TRIGGERED =>
+ * TRANSFER_TO_THIS_DEVICE_(SUCCEEDED|FAILED)
+ *
+ * This method should validate that the states go through approximately that sequence.
+ *
+ * See b/221265848 for more details.
+ */
abstract fun isValidNextState(nextState: ChipStateSender): Boolean
companion object {
@@ -276,6 +290,18 @@
return currentState.isValidNextState(desiredState)
}
+
+ /**
+ * Returns true if [state] represents a state at the beginning of a sequence and false
+ * otherwise.
+ */
+ private fun stateIsStartOfSequence(state: ChipStateSender): Boolean {
+ return state == FAR_FROM_RECEIVER ||
+ state.transferStatus == TransferStatus.NOT_STARTED ||
+ // It's possible to skip the NOT_STARTED phase and go immediately into the
+ // IN_PROGRESS phase.
+ state.transferStatus == TransferStatus.IN_PROGRESS
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 79b4b3a..35423f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -191,7 +191,7 @@
private static final String EXTRA_TRANSIENT_STATE = "transient_state";
/** Allow some time inbetween the long press for back and recents. */
- private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
+ private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200;
private static final long AUTODIM_TIMEOUT_MS = 2250;
private final Context mContext;
@@ -1439,7 +1439,7 @@
// If we recently long-pressed the other button then they were
// long-pressed 'together'
- if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
+ if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERANCE) {
stopLockTaskMode = true;
return true;
} else if (v.getId() == btnId1) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 8d8903d..2522e1c 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -75,6 +75,8 @@
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.volume.Events;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
@@ -83,8 +85,6 @@
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
*/
@SysUISingleton
@@ -132,7 +132,6 @@
private static final String ACTION_AUTO_SAVER_NO_THANKS =
"PNW.autoSaverNoThanks";
- private static final String ACTION_ENABLE_SEVERE_BATTERY_DIALOG = "PNW.enableSevereDialog";
private static final String EXTRA_SCHEDULED_BY_PERCENTAGE =
"extra_scheduled_by_percentage";
public static final String BATTERY_SAVER_SCHEDULE_SCREEN_INTENT_ACTION =
@@ -155,7 +154,6 @@
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
private final Intent mOpenBatterySaverSettings =
settings(Settings.ACTION_BATTERY_SAVER_SETTINGS);
- private final boolean mUseSevereDialog;
private int mBatteryLevel;
private int mBucket;
@@ -177,7 +175,6 @@
private ActivityStarter mActivityStarter;
private final BroadcastSender mBroadcastSender;
private final UiEventLogger mUiEventLogger;
- private GlobalSettings mGlobalSettings;
private final UserTracker mUserTracker;
private final Lazy<BatteryController> mBatteryControllerLazy;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -198,9 +195,7 @@
mBroadcastSender = broadcastSender;
mBatteryControllerLazy = batteryControllerLazy;
mDialogLaunchAnimator = dialogLaunchAnimator;
- mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
mUiEventLogger = uiEventLogger;
- mGlobalSettings = globalSettings;
mUserTracker = userTracker;
}
@@ -287,20 +282,6 @@
}
protected void showWarningNotification() {
- if (mGlobalSettings.getInt(Global.LOW_POWER_MODE_REMINDER_ENABLED, 1) == 0) {
- return;
- }
- if (showSevereLowBatteryDialog()) {
- mBroadcastSender.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG)
- .setPackage(mContext.getPackageName())
- .putExtra(EXTRA_SCHEDULED_BY_PERCENTAGE, isScheduledByPercentage())
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND));
- // Reset the state once dialog been enabled
- dismissLowBatteryNotification();
- mPlaySound = false;
- return;
- }
if (isScheduledByPercentage()) {
return;
}
@@ -349,10 +330,6 @@
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
- private boolean showSevereLowBatteryDialog() {
- return mBucket < -1 && mUseSevereDialog;
- }
-
/**
* Checking battery saver schedule mode is set as "Based on percentage" or not.
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 28dd986..d423048 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -259,17 +259,19 @@
Log.d(TAG, "setWifiIndicators: " + indicators);
}
mWifiInfo.mEnabled = indicators.enabled;
- if (indicators.qsIcon == null) {
- return;
- }
- mWifiInfo.mConnected = indicators.qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
- mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
- refreshState(mWifiInfo);
+ if (indicators.qsIcon != null) {
+ mWifiInfo.mConnected = indicators.qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ refreshState(mWifiInfo);
+ } else {
+ mWifiInfo.mConnected = false;
+ mWifiInfo.mWifiSignalIconId = 0;
+ mWifiInfo.mWifiSignalContentDescription = null;
+ }
}
@Override
@@ -533,6 +535,9 @@
if (DEBUG) {
Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
}
+ if (!cb.mConnected) {
+ return;
+ }
final Resources r = mContext.getResources();
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
index 70ea2b5..48aa60f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -17,8 +17,10 @@
package com.android.systemui.screenshot
import android.content.pm.PackageManager
+import android.view.Display
import android.view.IWindowManager
import android.view.ViewGroup
+import android.view.WindowManager
import android.widget.TextView
import com.android.systemui.R
import javax.inject.Inject
@@ -34,8 +36,18 @@
* notified.
*/
fun maybeNotifyOfScreenshot(data: ScreenshotData): List<CharSequence> {
- // TODO: actually ask the window manager once API is available.
- return listOf()
+ // No notification for screenshots from overview.
+ if (data.source == WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW) return listOf()
+
+ // Notify listeners, retrieve a list of listening components.
+ val components = windowManager.notifyScreenshotListeners(Display.DEFAULT_DISPLAY)
+
+ // Convert component names to app names.
+ return components.map {
+ packageManager
+ .getActivityInfo(it, PackageManager.ComponentInfoFlags.of(0))
+ .loadLabel(packageManager)
+ }
}
fun populateView(view: ViewGroup, appNames: List<CharSequence>) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index c891686..7a62bae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -46,6 +46,8 @@
SCREENSHOT_SAVED(306),
@UiEvent(doc = "screenshot failed to save")
SCREENSHOT_NOT_SAVED(336),
+ @UiEvent(doc = "failed to capture screenshot")
+ SCREENSHOT_CAPTURE_FAILED(1281),
@UiEvent(doc = "screenshot preview tapped")
SCREENSHOT_PREVIEW_TAPPED(307),
@UiEvent(doc = "screenshot edit button tapped")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 4214c8f..8035d19 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -26,6 +26,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import android.annotation.MainThread;
@@ -202,6 +203,7 @@
// animation and error notification.
if (!mUserManager.isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
+ logFailedRequest(request);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_save_user_locked_text);
callback.reportError();
@@ -212,6 +214,7 @@
mBgExecutor.execute(() -> {
Log.w(TAG, "Skipping screenshot because an IT admin has disabled "
+ "screenshots on the device");
+ logFailedRequest(request);
String blockedByAdminText = mDevicePolicyManager.getResources().getString(
SCREENSHOT_BLOCKED_BY_ADMIN,
() -> mContext.getString(R.string.screenshot_blocked_by_admin));
@@ -225,38 +228,43 @@
if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
Log.d(TAG, "Processing screenshot data");
ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
- mProcessor.processAsync(screenshotData,
- (data) -> dispatchToController(data, onSaved, callback));
+ try {
+ mProcessor.processAsync(screenshotData,
+ (data) -> dispatchToController(data, onSaved, callback));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to process screenshot request!", e);
+ logFailedRequest(request);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
+ }
} else {
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
+ try {
+ mProcessor.processAsync(request,
+ (r) -> dispatchToController(r, onSaved, callback));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to process screenshot request!", e);
+ logFailedRequest(request);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
+ }
}
}
private void dispatchToController(ScreenshotData screenshot,
Consumer<Uri> uriConsumer, RequestCallback callback) {
-
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
screenshot.getPackageNameString());
-
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
- && screenshot.getBitmap() == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- return;
- }
-
mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
}
private void dispatchToController(ScreenshotRequest request,
Consumer<Uri> uriConsumer, RequestCallback callback) {
-
ComponentName topComponent = request.getTopComponent();
- mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0,
- topComponent == null ? "" : topComponent.getPackageName());
+ String packageName = topComponent == null ? "" : topComponent.getPackageName();
+ mUiEventLogger.log(
+ ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
switch (request.getType()) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
@@ -275,21 +283,22 @@
int taskId = request.getTaskId();
int userId = request.getUserId();
- if (screenshot == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- } else {
- mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
- taskId, userId, topComponent, uriConsumer, callback);
- }
+ mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+ taskId, userId, topComponent, uriConsumer, callback);
break;
default:
- Log.w(TAG, "Invalid screenshot option: " + request.getType());
+ Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
}
}
+ private void logFailedRequest(ScreenshotRequest request) {
+ ComponentName topComponent = request.getTopComponent();
+ String packageName = topComponent == null ? "" : topComponent.getPackageName();
+ mUiEventLogger.log(
+ ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
+ mUiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, packageName);
+ }
+
private static void sendComplete(Messenger target) {
try {
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index d71fbf6..c8b6a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -7,6 +7,7 @@
per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
per-file *ShadeHeader* = kozynski@google.com, asc@google.com
+per-file *Shade* = justinweir@google.com
per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 4d7496d..02d0f94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -54,11 +54,13 @@
import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.statusbar.Weather
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
+import java.time.Instant
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -165,8 +167,19 @@
}
isContentUpdatedOnce = true
}
+
+ val now = Instant.now()
+ val weatherTarget = targets.find { t ->
+ t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
+ now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
+ now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
+ }
+ if (weatherTarget != null) {
+ val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
+ }
}
+
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
execution.assertIsMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 0c95eab..058042c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -71,9 +71,11 @@
* marking them as relevant for setup are allowed to show when device is unprovisioned
*/
private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
- // system_server checks the permission so systemui can just check whether the
- // extra exists
- return sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
+ final boolean hasPermission = checkUidPermission(
+ Manifest.permission.NOTIFICATION_DURING_SETUP,
+ sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
+ return hasPermission
+ && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
}
private int checkUidPermission(String permission, int uid) {
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 aab36da..1fb7eb5 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
@@ -147,7 +147,7 @@
private boolean mShadeNeedsToClose = false;
@VisibleForTesting
- static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
+ static final float RUBBER_BAND_FACTOR_NORMAL = 0.1f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index c248a50..b88531e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -352,7 +352,7 @@
}
private boolean willAnimateFromLockScreenToAod() {
- return getAlwaysOn() && mKeyguardVisible;
+ return shouldControlScreenOff() && mKeyguardVisible;
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 9ae38e9..0e4a432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,10 +38,12 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -100,6 +103,7 @@
constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val logger: ConnectivityPipelineLogger,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
@@ -168,6 +172,8 @@
}
}
}
+ .distinctUntilChanged()
+ .onEach { logger.logFilteredSubscriptionsChanged(it) }
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 829a5ca..ef75713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +32,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -51,13 +55,17 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ private val logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions.mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
+ interactor.filteredSubscriptions
+ .mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -72,6 +80,9 @@
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+ private var isCollecting: Boolean = false
+ private var lastValue: List<Int>? = null
+
override fun start() {
// Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if
@@ -79,8 +90,18 @@
// get the logging data without rendering.
if (statusBarPipelineFlags.useNewMobileIcons()) {
scope.launch {
- mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
+ isCollecting = true
+ mobileSubIds.collectLatest {
+ logger.logUiAdapterSubIdsSentToIconController(it)
+ lastValue = it
+ iconController.setNewMobileIconSubIds(it)
+ }
}
}
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("isCollecting=$isCollecting")
+ pw.println("Last values sent to icon controller: $lastValue")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 3e81c7c..a4b2abc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -29,6 +29,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
@@ -97,6 +98,12 @@
}
}
+ launch {
+ viewModel.contentDescription.distinctUntilChanged().collect {
+ ContentDescriptionViewBinder.bind(it, view)
+ }
+ }
+
// Set the network type icon
launch {
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 5e935616..9e2024a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -43,6 +45,7 @@
val subscriptionId: Int
/** An int consumable by [SignalDrawable] for display */
val iconId: Flow<Int>
+ val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
val networkTypeIcon: Flow<Icon?>
@@ -102,6 +105,23 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
+ override val contentDescription: Flow<ContentDescription> = run {
+ val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE)
+ combine(
+ iconInteractor.level,
+ iconInteractor.isInService,
+ ) { level, isInService ->
+ val resId =
+ when {
+ isInService -> PHONE_SIGNAL_STRENGTH[level]
+ else -> PHONE_SIGNAL_STRENGTH_NONE
+ }
+ ContentDescription.Resource(resId)
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
+
private val showNetworkTypeIcon: Flow<Boolean> =
combine(
iconInteractor.isDataConnected,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 491f3a5..7c7ffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -25,6 +25,7 @@
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -201,6 +202,35 @@
)
}
+ // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
+
+ fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Filtered subscriptions updated: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 307787b..6b507ba 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -115,6 +115,7 @@
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final boolean mIsMonochromaticEnabled;
private final Context mContext;
private final boolean mIsMonetEnabled;
private final UserTracker mUserTracker;
@@ -373,6 +374,7 @@
@Main Resources resources,
WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
+ mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 8cb4deb..e5ab473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.Context
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -30,6 +33,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
@@ -68,6 +73,9 @@
/** [UserInfo] of the currently-selected user. */
val selectedUserInfo: Flow<UserInfo>
+ /** Whether user switching is currently in progress. */
+ val userSwitchingInProgress: Flow<Boolean>
+
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
@@ -108,6 +116,8 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
+ private val activityManager: IActivityManager,
+ featureFlags: FeatureFlags,
) : UserRepository {
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
@@ -129,6 +139,10 @@
private var _isGuestUserResetting: Boolean = false
override var isGuestUserResetting: Boolean = _isGuestUserResetting
+ private val _isUserSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _isUserSwitchingInProgress
+
override val isGuestUserCreationScheduled = AtomicBoolean()
override val isStatusBarUserChipEnabled: Boolean =
@@ -141,6 +155,9 @@
init {
observeSelectedUser()
observeUserSettings()
+ if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
+ observeUserSwitching()
+ }
}
override fun refreshUsers() {
@@ -166,6 +183,28 @@
return _userSwitcherSettings.value.isSimpleUserSwitcher
}
+ private fun observeUserSwitching() {
+ conflatedCallbackFlow {
+ val callback =
+ object : UserSwitchObserver() {
+ override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+ trySendWithFailureLogging(true, TAG, "userSwitching started")
+ }
+
+ override fun onUserSwitchComplete(newUserId: Int) {
+ trySendWithFailureLogging(false, TAG, "userSwitching completed")
+ }
+ }
+ activityManager.registerUserSwitchObserver(callback, TAG)
+ trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+ }
+ .onEach { _isUserSwitchingInProgress.value = it }
+ // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
+ // once the flag is launched
+ .launchIn(applicationScope)
+ }
+
private fun observeSelectedUser() {
conflatedCallbackFlow {
fun send() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index b9c23d4..43a2017 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -32,10 +32,12 @@
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -72,7 +74,7 @@
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
@Mock private lateinit var clock: ClockController
- @Mock private lateinit var mainExecutor: Executor
+ @Mock private lateinit var mainExecutor: DelayableExecutor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var smallClockController: ClockFaceController
@@ -97,6 +99,8 @@
whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
+ whenever(smallClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
+ whenever(largeClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
repository = FakeKeyguardRepository()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 512c351..36b3f89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -100,6 +101,8 @@
@Mock
private ClockEvents mClockEvents;
@Mock
+ private ClockFaceEvents mClockFaceEvents;
+ @Mock
DumpManager mDumpManager;
@Mock
ClockEventController mClockEventController;
@@ -176,6 +179,8 @@
when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
when(mClockController.getEvents()).thenReturn(mClockEvents);
+ when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+ when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index e918c1c..fc11148 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -88,6 +89,7 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -219,11 +221,14 @@
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
- mExecutor));
+ mExecutor,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
- mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+ mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mAuthController) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index aac0b51..3da7261 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,8 +19,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
-import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -46,7 +44,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -74,6 +71,7 @@
public MockitoRule mockito = MockitoJUnit.rule();
private Context mContextWrapper;
+ private WindowManager mWindowManager;
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
@@ -97,6 +95,7 @@
}
};
+ mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -158,7 +157,8 @@
public void onKeyguardVisibilityChanged_showing_destroyWidget() {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
@@ -184,7 +184,8 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -197,7 +198,8 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -318,40 +320,18 @@
}
@Test
- public void onTargetsChanged_flingSpringAnimationsDisabled_floatingMenuIsCreated() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false);
-
- mController = setUpController();
- mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
-
- assertThat(mController.mFloatingMenu).isInstanceOf(AccessibilityFloatingMenu.class);
- }
-
- @Test
public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
UserHandle.USER_CURRENT);
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, true);
- mController = setUpController(featureFlags);
+ mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
assertThat(mController.mFloatingMenu).isInstanceOf(MenuViewLayerController.class);
}
private AccessibilityFloatingMenuController setUpController() {
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false);
- return setUpController(featureFlags);
- }
-
- private AccessibilityFloatingMenuController setUpController(FakeFeatureFlags featureFlags) {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
@@ -361,7 +341,7 @@
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
- mKeyguardUpdateMonitor, featureFlags, mSecureSettings, displayTracker);
+ mKeyguardUpdateMonitor, mSecureSettings, displayTracker);
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
deleted file mode 100644
index 04345fd..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests for {@link AccessibilityFloatingMenu}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AccessibilityFloatingMenuTest extends SysuiTestCase {
-
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private AccessibilityManager mAccessibilityManager;
- @Mock
- private SecureSettings mSecureSettings;
-
- private AccessibilityFloatingMenuView mMenuView;
- private AccessibilityFloatingMenu mMenu;
-
- @Before
- public void initMenu() {
- final List<String> assignedTargets = new ArrayList<>();
- mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
- assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
- doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
- anyInt());
-
- final Position position = new Position(0, 0);
- mMenuView = new AccessibilityFloatingMenuView(mContext, position);
- mMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings, mMenuView);
- }
-
- @Test
- public void showMenuView_success() {
- mMenu.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- }
-
- @Test
- public void hideMenuView_success() {
- mMenu.show();
- mMenu.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- }
-
- @Test
- public void showMenuView_emptyTarget_notShow() {
- final List<String> emptyTargets = new ArrayList<>();
- doReturn(emptyTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
- anyInt());
-
- mMenu.show();
-
- assertThat(mMenuView.isShowing()).isFalse();
- }
-
- @After
- public void tearDown() {
- mMenu.hide();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
deleted file mode 100644
index bebd871..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.View.OVER_SCROLL_ALWAYS;
-import static android.view.View.OVER_SCROLL_NEVER;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.systemBars;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/** Tests for {@link AccessibilityFloatingMenuView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
-
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
- private final List<AccessibilityTarget> mTargets = new ArrayList<>(
- Collections.singletonList(mock(AccessibilityTarget.class)));
-
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private ViewPropertyAnimator mAnimator;
- @Mock
- private WindowMetrics mWindowMetrics;
- private MotionEvent mInterceptMotionEvent;
- private AccessibilityFloatingMenuView mMenuView;
- private RecyclerView mListView = new RecyclerView(mContext);
-
- private int mMenuWindowHeight;
- private int mMenuHalfWidth;
- private int mMenuHalfHeight;
- private int mDisplayHalfWidth;
- private int mDisplayHalfHeight;
- private int mMaxWindowX;
- private int mMaxWindowY;
- private final int mDisplayWindowWidth = 1080;
- private final int mDisplayWindowHeight = 2340;
-
- @Before
- public void initMenuView() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, mDisplayWindowWidth,
- mDisplayWindowHeight));
- when(mWindowMetrics.getWindowInsets()).thenReturn(fakeDisplayInsets());
- mMenuView = spy(
- new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
- }
-
- @Before
- public void setUpMatrices() {
- final Resources res = mContext.getResources();
- final int margin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
- final int padding =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding);
- final int iconWidthHeight =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
- final int menuWidth = padding * 2 + iconWidthHeight;
- final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
- mMenuHalfWidth = menuWidth / 2;
- mMenuHalfHeight = menuHeight / 2;
- mDisplayHalfWidth = mDisplayWindowWidth / 2;
- mDisplayHalfHeight = mDisplayWindowHeight / 2;
- int marginStartEnd =
- mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT
- ? margin : 0;
- mMaxWindowX = mDisplayWindowWidth - marginStartEnd - menuWidth;
- mMenuWindowHeight = menuHeight + margin * 2;
- mMaxWindowY = mDisplayWindowHeight - mMenuWindowHeight;
- }
-
- @Test
- public void initListView_success() {
- assertThat(mListView.getCompatAccessibilityDelegate()).isNotNull();
- assertThat(mMenuView.getChildCount()).isEqualTo(1);
- }
-
- @Test
- public void showMenuView_success() {
- mMenuView.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- verify(mWindowManager).addView(eq(mMenuView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void showMenuView_showTwice_addViewOnce() {
- mMenuView.show();
- mMenuView.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- verify(mWindowManager, times(1)).addView(eq(mMenuView),
- any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void hideMenuView_success() {
- mMenuView.show();
- mMenuView.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- verify(mWindowManager).removeView(eq(mMenuView));
- }
-
- @Test
- public void hideMenuView_hideTwice_removeViewOnce() {
- mMenuView.show();
- mMenuView.hide();
- mMenuView.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- verify(mWindowManager, times(1)).removeView(eq(mMenuView));
- }
-
- @Test
- public void hideMenuViewWhenStartingAnimation_animatorNotRunning() {
- mMenuView.show();
-
- mMenuView.mDragAnimator.start();
- mMenuView.hide();
-
- assertThat(mMenuView.mDragAnimator.isRunning()).isFalse();
- }
-
- @Test
- public void onTargetsChanged_singleTarget_expectedRadii() {
- final Position alignRightPosition = new Position(1.0f, 0.0f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition);
- setupBasicMenuView(menuView);
-
- menuView.onTargetsChanged(mTargets);
-
- final View view = menuView.getChildAt(0);
- final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground();
- final GradientDrawable gradientDrawable =
- (GradientDrawable) layerDrawable.getDrawable(0);
- final float smallRadius =
- getContext().getResources().getDimensionPixelSize(
- R.dimen.accessibility_floating_menu_small_single_radius);
- final float[] expectedRadii =
- new float[]{smallRadius, smallRadius, 0.0f, 0.0f, 0.0f, 0.0f, smallRadius,
- smallRadius};
- assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
- }
-
- @Test
- public void setSizeType_alignRightAndLargeSize_expectedRadii() {
- final RecyclerView listView = spy(new RecyclerView(mContext));
- final Position alignRightPosition = new Position(1.0f, 0.0f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition, listView);
- setupBasicMenuView(menuView);
-
- menuView.setSizeType(/* largeSize */ 1);
-
- final LayerDrawable layerDrawable =
- (LayerDrawable) listView.getBackground();
- final GradientDrawable gradientDrawable =
- (GradientDrawable) layerDrawable.getDrawable(0);
- final float largeRadius = getContext().getResources().getDimensionPixelSize(
- R.dimen.accessibility_floating_menu_large_single_radius);
- final float[] expectedRadii =
- new float[] {largeRadius, largeRadius, 0.0f, 0.0f, 0.0f, 0.0f, largeRadius,
- largeRadius};
- assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
- }
-
- @Test
- public void setShapeType_halfCircle_translationX() {
- final RecyclerView listView = spy(new RecyclerView(mContext));
- final AccessibilityFloatingMenuView menuView =
- new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, listView);
- setupBasicMenuView(menuView);
- doReturn(mAnimator).when(listView).animate();
-
- menuView.setShapeType(/* halfOvalShape */ 1);
-
- verify(mAnimator).translationX(anyFloat());
- }
-
- @Test
- public void onTargetsChanged_fadeInOut() {
- final InOrder inOrderMenuView = inOrder(mMenuView);
-
- mMenuView.onTargetsChanged(mTargets);
-
- inOrderMenuView.verify(mMenuView).fadeIn();
- inOrderMenuView.verify(mMenuView).fadeOut();
- }
-
- @Test
- public void setSizeType_fadeInOut() {
- final InOrder inOrderMenuView = inOrder(mMenuView);
-
- mMenuView.setSizeType(/* smallSize */ 0);
-
- inOrderMenuView.verify(mMenuView).fadeIn();
- inOrderMenuView.verify(mMenuView).fadeOut();
- }
-
- @Test
- public void tapOnAndDragMenu_interceptUpEvent() {
- final RecyclerView listView = new RecyclerView(mContext);
- final TestAccessibilityFloatingMenu menuView =
- new TestAccessibilityFloatingMenu(mContext, mPlaceholderPosition, listView);
- setupBasicMenuView(menuView);
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* displayCenterX */mDisplayHalfWidth
- - /* offsetXToDisplayLeftHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* displayCenterX */ mDisplayHalfWidth
- - /* offsetXToDisplayLeftHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
-
- assertThat(mInterceptMotionEvent.getAction()).isEqualTo(MotionEvent.ACTION_UP);
- }
-
- @Test
- public void tapOnAndDragMenu_matchLocation() {
- final float expectedX = 1.0f;
- final float expectedY = 0.7f;
- final Position position = new Position(expectedX, expectedY);
- final RecyclerView listView = new RecyclerView(mContext);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- position, listView);
- setupBasicMenuView(menuView);
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* displayCenterX */mDisplayHalfWidth
- + /* offsetXToDisplayRightHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* displayCenterX */ mDisplayHalfWidth
- + /* offsetXToDisplayRightHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
- menuView.mDragAnimator.end();
-
- assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
- assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- /* newWindowY = displayCenterY - offsetY */ mDisplayHalfHeight - mMenuHalfHeight);
- }
-
-
- @Test
- public void tapOnAndDragMenuToDisplaySide_transformShapeHalfOval() {
- final Position alignRightPosition = new Position(1.0f, 0.8f);
- final RecyclerView listView = new RecyclerView(mContext);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition, listView);
- setupBasicMenuView(menuView);
-
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
- /* downY */ (currentWindowY + mMenuHalfHeight));
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
- /* downY */ (currentWindowY + mMenuHalfHeight));
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
-
- assertThat(menuView.mShapeType).isEqualTo(/* halfOval */ 1);
- }
-
- @Test
- public void onTargetsChanged_exceedAvailableHeight_overScrollAlways() {
- doReturn(true).when(mMenuView).hasExceededMaxLayoutHeight();
-
- mMenuView.onTargetsChanged(mTargets);
-
- assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_ALWAYS);
- }
-
- @Test
- public void onTargetsChanged_notExceedAvailableHeight_overScrollNever() {
- doReturn(false).when(mMenuView).hasExceededMaxLayoutHeight();
-
- mMenuView.onTargetsChanged(mTargets);
-
- assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_NEVER);
- }
-
- @Test
- public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() {
- final int offset = 200;
-
- final Position alignRightPosition = new Position(1.0f, 0.8f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition);
- setupBasicMenuView(menuView);
- final WindowInsets imeInset = fakeImeInsetWith(menuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- final int expectedLayoutY = menuView.mCurrentLayoutParams.y - offset;
- menuView.dispatchApplyWindowInsets(imeInset);
-
- assertThat(menuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
- }
-
- @Test
- public void hideIme_onMenuViewShifted_menuViewMovedBack() {
- final int offset = 200;
- setupBasicMenuView(mMenuView);
- final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y;
- mMenuView.dispatchApplyWindowInsets(imeInset);
-
- mMenuView.dispatchApplyWindowInsets(
- new WindowInsets.Builder().setVisible(ime(), false).build());
-
- assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
- }
-
- @Test
- public void showMenuAndIme_withHigherIme_alignDisplayTopEdge() {
- final int offset = 99999;
-
- setupBasicMenuView(mMenuView);
- final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- mMenuView.dispatchApplyWindowInsets(imeInset);
-
- assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0);
- }
-
- @Test
- public void testConstructor_withPosition_expectedPosition() {
- final float expectedX = 1.0f;
- final float expectedY = 0.7f;
- final Position position = new Position(expectedX, expectedY);
-
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- position);
- setupBasicMenuView(menuView);
-
- assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
- assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- expectedY * mMaxWindowY);
- }
-
- @After
- public void tearDown() {
- mInterceptMotionEvent = null;
- mMotionEventHelper.recycleEvents();
- mListView = null;
- }
-
- private void setupBasicMenuView(AccessibilityFloatingMenuView menuView) {
- menuView.show();
- menuView.onTargetsChanged(mTargets);
- menuView.setSizeType(0);
- menuView.setShapeType(0);
- }
-
- /**
- * Based on the current menu status, fake the ime inset component {@link WindowInsets} used
- * for testing.
- *
- * @param menuView {@link AccessibilityFloatingMenuView} that needs to be changed
- * @param offset is used for the y-axis position of ime higher than the y-axis position of menu
- * @return the ime inset
- */
- private WindowInsets fakeImeInsetWith(AccessibilityFloatingMenuView menuView, int offset) {
- // Ensure the keyboard has overlapped on the menu view.
- final int fakeImeHeight =
- mDisplayWindowHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight)
- + offset;
- return new WindowInsets.Builder()
- .setVisible(ime(), true)
- .setInsets(ime(), Insets.of(0, 0, 0, fakeImeHeight))
- .build();
- }
-
- private WindowInsets fakeDisplayInsets() {
- final int fakeStatusBarHeight = 75;
- final int fakeNavigationBarHeight = 125;
- return new WindowInsets.Builder()
- .setVisible(systemBars() | displayCutout(), true)
- .setInsets(systemBars() | displayCutout(),
- Insets.of(0, fakeStatusBarHeight, 0, fakeNavigationBarHeight))
- .build();
- }
-
- private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
- TestAccessibilityFloatingMenu(Context context, Position position, RecyclerView listView) {
- super(context, position, listView);
- }
-
- @Override
- public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
- @NonNull MotionEvent event) {
- final boolean intercept = super.onInterceptTouchEvent(recyclerView, event);
-
- if (intercept) {
- mInterceptMotionEvent = event;
- }
-
- return intercept;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
deleted file mode 100644
index 3553a0a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link BaseTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class BaseTooltipViewTest extends SysuiTestCase {
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
-
- private AccessibilityFloatingMenuView mMenuView;
- private BaseTooltipView mToolTipView;
-
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mMenuView = new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition);
- mToolTipView = new BaseTooltipView(mContext, mMenuView);
- }
-
- @Test
- public void showToolTipView_success() {
- mToolTipView.show();
-
- verify(mWindowManager).addView(eq(mToolTipView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void touchOutsideWhenToolTipViewShown_dismiss() {
- final MotionEvent outsideEvent =
- mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0,
- /* eventTime= */1,
- MotionEvent.ACTION_OUTSIDE,
- /* x= */ 0,
- /* y= */ 0);
-
- mToolTipView.show();
- mToolTipView.dispatchTouchEvent(outsideEvent);
-
- verify(mWindowManager).removeView(mToolTipView);
- }
-
- @Test
- public void getAccessibilityActionList_matchResult() {
- final AccessibilityNodeInfo infos = new AccessibilityNodeInfo();
- mToolTipView.onInitializeAccessibilityNodeInfo(infos);
-
- assertThat(infos.getActionList().size()).isEqualTo(1);
- }
-
- @Test
- public void accessibilityAction_dismiss_success() {
- final BaseTooltipView tooltipView =
- spy(new BaseTooltipView(mContext, mMenuView));
-
- final boolean isActionPerformed =
- tooltipView.performAccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS.getId(),
- /* arguments= */ null);
-
- assertThat(isActionPerformed).isTrue();
- verify(tooltipView).hide();
- }
-
- @After
- public void tearDown() {
- mToolTipView.hide();
- mMotionEventHelper.recycleEvents();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
deleted file mode 100644
index 9eba49d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link DockTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class DockTooltipViewTest extends SysuiTestCase {
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
-
- private AccessibilityFloatingMenuView mMenuView;
- private DockTooltipView mDockTooltipView;
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mMenuView = spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition));
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @Test
- public void showTooltip_success() {
- mDockTooltipView.show();
-
- verify(mMenuView).startTranslateXAnimation();
- verify(mWindowManager).addView(eq(mDockTooltipView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void hideTooltip_success() {
- mDockTooltipView.show();
- mDockTooltipView.hide();
-
- verify(mMenuView).stopTranslateXAnimation();
- verify(mWindowManager).removeView(mDockTooltipView);
- }
-
- @Test
- public void touchOutsideWhenToolTipViewShown_stopAnimation() {
- final MotionEvent outsideEvent =
- mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0,
- /* eventTime= */ 1,
- MotionEvent.ACTION_OUTSIDE,
- /* x= */ 0,
- /* y= */ 0);
-
- mDockTooltipView.show();
- mDockTooltipView.dispatchTouchEvent(outsideEvent);
-
- verify(mMenuView).stopTranslateXAnimation();
- }
-
- @After
- public void tearDown() {
- mMotionEventHelper.recycleEvents();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
deleted file mode 100644
index ea104a7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/** Tests for {@link ItemDelegateCompat}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ItemDelegateCompatTest extends SysuiTestCase {
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
- private RecyclerView mListView;
- private AccessibilityFloatingMenuView mMenuView;
- private ItemDelegateCompat mItemDelegateCompat;
- private final Rect mAvailableBounds = new Rect(100, 200, 300, 400);
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Before
- public void setUp() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mListView = new RecyclerView(mContext);
- mMenuView =
- spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
- mItemDelegateCompat =
- new ItemDelegateCompat(new RecyclerViewAccessibilityDelegate(mListView), mMenuView);
- }
-
- @Test
- public void getAccessibilityActionList_matchResult() {
- final AccessibilityNodeInfoCompat info =
- new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
-
- mItemDelegateCompat.onInitializeAccessibilityNodeInfo(mListView, info);
-
- assertThat(info.getActionList().size()).isEqualTo(5);
- }
-
- @Test
- public void performAccessibilityMoveTopLeftAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveTopLeftAction =
- mItemDelegateCompat.performAccessibilityAction(mListView, R.id.action_move_top_left,
- null);
-
- assertThat(moveTopLeftAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.top);
- }
-
- @Test
- public void performAccessibilityMoveTopRightAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveTopRightAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_top_right, null);
-
- assertThat(moveTopRightAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.top);
- }
-
- @Test
- public void performAccessibilityMoveBottomLeftAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveBottomLeftAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_bottom_left, null);
-
- assertThat(moveBottomLeftAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.bottom);
- }
-
- @Test
- public void performAccessibilityMoveBottomRightAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveBottomRightAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_bottom_right, null);
-
- assertThat(moveBottomRightAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.bottom);
- }
-
- @Test
- public void performAccessibilityMoveOutEdgeAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveOutEdgeAndShowAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_out_edge_and_show, null);
-
- assertThat(moveOutEdgeAndShowAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- }
-
- @Test
- public void setupAccessibilityActions_oval_hasActionMoveToEdgeAndHide() {
- final AccessibilityNodeInfoCompat info =
- new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
- mMenuView.setShapeType(/* ovalShape */ 0);
-
- mItemDelegateCompat.onInitializeAccessibilityNodeInfo(mListView, info);
-
- assertThat(info.getActionList().stream().anyMatch(
- action -> action.getId() == R.id.action_move_to_edge_and_hide)).isTrue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java
deleted file mode 100644
index 2fb0a90..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.text.SpannableString;
-import android.text.method.LinkMovementMethod;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link MigrationTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class MigrationTooltipViewTest extends SysuiTestCase {
-
- private TextView mTextView;
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Before
- public void setUp() {
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- mPlaceholderPosition);
- final MigrationTooltipView toolTipView = new MigrationTooltipView(mContext, menuView);
- mTextView = toolTipView.findViewById(R.id.text);
- }
-
- @Test
- public void onCreate_setLinkMovementMethod() {
- assertThat(mTextView.getMovementMethod()).isInstanceOf(LinkMovementMethod.class);
- }
-
- @Test
- public void onCreate_setDescription_matchTextAndSpanNum() {
- final CharSequence expectedTextWithoutSpan =
- AnnotationLinkSpan.linkify(mContext.getText(
- R.string.accessibility_floating_button_migration_tooltip)).toString();
- final SpannableString spannableString = (SpannableString) mTextView.getText();
- final int AnnotationLinkSpanNum =
- spannableString.getSpans(/* queryStart= */ 0, spannableString.length(),
- AnnotationLinkSpan.class).length;
-
- assertThat(AnnotationLinkSpanNum).isEqualTo(1);
- assertThat(mTextView.getText().toString().contentEquals(expectedTextWithoutSpan)).isTrue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index b5c0f20..41beada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -58,7 +58,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
@@ -137,7 +137,7 @@
alternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- FakeBiometricRepository(),
+ FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index d2183b3..c73ff1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
@@ -92,7 +92,7 @@
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- mock(BiometricRepository::class.java),
+ mock(BiometricSettingsRepository::class.java),
mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 71c335e..7177919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.clipboardoverlay;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
+
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals;
@@ -38,6 +40,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -60,6 +63,7 @@
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
+ private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private UiEventLogger mUiEventLogger;
@@ -93,8 +97,10 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
+
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
}
@@ -187,4 +193,34 @@
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
}
+
+ @Test
+ public void test_minimizedLayoutFlagOff_usesLegacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipDataLegacy(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
+
+ @Test
+ public void test_minimizedLayoutFlagOn_usesNew() {
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
new file mode 100644
index 0000000..faef35e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.PersistableBundle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClipboardModelTest : SysuiTestCase() {
+ @Mock private lateinit var mClipboardUtils: ClipboardOverlayUtils
+ @Mock private lateinit var mMockContext: Context
+ @Mock private lateinit var mMockContentResolver: ContentResolver
+ private lateinit var mSampleClipData: ClipData
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
+ }
+
+ @Test
+ fun test_nullClipData() {
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
+ assertNull(model.clipData)
+ assertEquals("test source", model.source)
+ assertEquals(ClipboardModel.Type.OTHER, model.type)
+ assertNull(model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_textClipData() {
+ val source = "test source"
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, source)
+ assertEquals(mSampleClipData, model.clipData)
+ assertEquals(source, model.source)
+ assertEquals(ClipboardModel.Type.TEXT, model.type)
+ assertEquals(mSampleClipData.getItemAt(0), model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_sensitiveExtra() {
+ val description = mSampleClipData.description
+ val b = PersistableBundle()
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
+ description.extras = b
+ val data = ClipData(description, mSampleClipData.getItemAt(0))
+ val (_, _, _, _, sensitive) =
+ ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
+ assertTrue(sensitive)
+ }
+
+ @Test
+ fun test_remoteExtra() {
+ whenever(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true)
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, "")
+ assertTrue(model.isRemote)
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData() {
+ val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData_loadFailure() {
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenThrow(IOException())
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertNull(model.loadThumbnail(mMockContext))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index ca5b7af..0ac2667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -16,13 +16,17 @@
package com.android.systemui.clipboardoverlay;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -35,8 +39,11 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.view.WindowInsets;
import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
@@ -102,11 +109,14 @@
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 0)));
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
mOverlayController = new ClipboardOverlayController(
mContext,
@@ -118,8 +128,7 @@
mFeatureFlags,
mClipboardUtils,
mExecutor,
- mUiEventLogger,
- mDisplayTracker);
+ mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@@ -130,6 +139,159 @@
}
@Test
+ public void test_setClipData_nullData_legacy() {
+ ClipData clipData = null;
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_invalidImageData_legacy() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData_legacy() {
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipDataLegacy(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls_legacy() {
+ when(mAnimator.isRunning()).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onShareTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onShareButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onDismissTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_multipleDismissals_dismissesOnce_legacy() {
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOn_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler, never()).resetTimeout();
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOff_legacy() {
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_nonRemoteCopy_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_logsUseLastClipSource_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
+ mCallbacks.onDismissButtonTapped();
+ mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ @Test
+ public void test_logOnClipboardActionsShown_legacy() {
+ ClipData.Item item = mSampleClipData.getItemAt(0);
+ item.setTextLinks(Mockito.mock(TextLinks.class));
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
+ .thenReturn(true);
+ when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+ .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
+ when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }
+ });
+
+ mOverlayController.setClipDataLegacy(
+ new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
+ mExecutor.runAllReady();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ // start of refactored setClipData tests
+ @Test
public void test_setClipData_nullData() {
ClipData clipData = null;
mOverlayController.setClipData(clipData, "");
@@ -280,4 +442,43 @@
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
verifyNoMoreInteractions(mUiEventLogger);
}
+
+ @Test
+ public void test_noInsets_showsExpanded() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insets_showsMinimized() {
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 1)));
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView).setMinimized(true);
+ verify(mClipboardOverlayView, never()).setMinimized(false);
+ verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
+
+ mCallbacks.onMinimizedViewTapped();
+
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insetsChanged_minimizes() {
+ mOverlayController.setClipData(mSampleClipData, "");
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+
+ WindowInsets insetsWithKeyboard = getImeInsets(new Rect(0, 0, 0, 1));
+ mOverlayController.onInsetsChanged(insetsWithKeyboard, ORIENTATION_PORTRAIT);
+ verify(mClipboardOverlayView).setMinimized(true);
+ }
+
+ private static WindowInsets getImeInsets(Rect r) {
+ return new WindowInsets.Builder().setInsets(WindowInsets.Type.ime(), Insets.of(r)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index d54babf..e35b2a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -104,8 +104,6 @@
ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
- private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
- @Captor
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -178,10 +176,6 @@
)
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
- verify(userTracker).addCallback(
- capture(userTrackerCallbackCaptor), any()
- )
-
verify(listingController).addCallback(capture(listingCallbackCaptor))
}
@@ -539,7 +533,7 @@
reset(persistenceWrapper)
- userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext)
+ controller.changeUser(UserHandle.of(otherUser))
verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
verify(persistenceWrapper).readFavorites()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
new file mode 100644
index 0000000..7ecaca6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.start
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsStartableTest : SysuiTestCase() {
+
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var userTracker: UserTracker
+
+ private lateinit var fakeExecutor: FakeExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf<String>()
+ )
+
+ fakeExecutor = FakeExecutor(FakeSystemClock())
+ }
+
+ @Test
+ fun testDisabledNothingIsCalled() {
+ createStartable(enabled = false).start()
+
+ verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+ }
+
+ @Test
+ fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPreferredPackagesNotInstalled_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ `when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPreferredPackageNotPanel_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testExistingSelection_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(mock<SelectedItem.PanelItem>())
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPanelAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+ }
+
+ @Test
+ fun testMultiplePreferredOnlyOnePanel_panelAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf("other_package", TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings =
+ listOf(
+ ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
+ ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
+ )
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+ }
+
+ @Test
+ fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL, "other_package")
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings =
+ listOf(
+ ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
+ ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
+ )
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
+ }
+
+ @Test
+ fun testPreferredSelectionIsPanel_bindOnStart() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
+ }
+
+ @Test
+ fun testPreferredSelectionPanel_listingNoPanel_notBind() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).bindComponentForPanel(any())
+ }
+
+ @Test
+ fun testNotPanelSelection_noBind() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).bindComponentForPanel(any())
+ }
+
+ private fun createStartable(enabled: Boolean): ControlsStartable {
+ val component: ControlsComponent =
+ mock() {
+ `when`(isEnabled()).thenReturn(enabled)
+ if (enabled) {
+ `when`(getControlsController()).thenReturn(Optional.of(controlsController))
+ `when`(getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+ } else {
+ `when`(getControlsController()).thenReturn(Optional.empty())
+ `when`(getControlsListingController()).thenReturn(Optional.empty())
+ }
+ }
+ return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem {
+ if (panelActivity == null) {
+ throw IllegalArgumentException("$this is not a panel")
+ }
+ return SelectedItem.PanelItem(loadLabel(), componentName)
+ }
+
+ private const val TEST_PACKAGE = "pkg"
+ private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index b7d0f29..0f25764 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -289,4 +289,36 @@
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.areEntryAnimationsFinished()).isTrue();
}
+
+ @Test
+ public void testNotifyDreamOverlayStatusBarVisibleChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor, true);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.isDreamOverlayStatusBarVisible()).isFalse();
+
+ stateController.setDreamOverlayStatusBarVisible(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.isDreamOverlayStatusBarVisible()).isTrue();
+ }
+
+ @Test
+ public void testNotifyHasAssistantAttentionChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor, true);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.hasAssistantAttention()).isFalse();
+
+ stateController.setHasAssistantAttention(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.hasAssistantAttention()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 596b903..d16b757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -468,6 +468,21 @@
}
@Test
+ public void testAssistantAttentionIconShownWhenAttentionGained() {
+ mController.onViewAttached();
+
+ when(mDreamOverlayStateController.hasAssistantAttention()).thenReturn(true);
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ verify(mView).showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, true, null);
+ }
+
+ @Test
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
@@ -572,6 +587,13 @@
assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testDreamOverlayStatusBarVisibleSetToFalseOnDetach() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+ verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
+ }
+
private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
final ArgumentCaptor<StatusBarWindowStateListener>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
new file mode 100644
index 0000000..ef1061f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.dreams.conditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Condition;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AssistantAttentionConditionTest extends SysuiTestCase {
+ @Mock
+ Condition.Callback mCallback;
+ @Mock
+ AssistUtils mAssistUtils;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ private AssistantAttentionCondition mAssistantAttentionCondition;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mAssistantAttentionCondition =
+ new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
+ // Adding a callback also starts the condition.
+ mAssistantAttentionCondition.addCallback(mCallback);
+ }
+
+ @Test
+ public void testEnableVisualQueryDetection() {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).enableVisualQueryDetection(any());
+ }
+
+ @Test
+ public void testDisableVisualQueryDetection() {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).disableVisualQueryDetection();
+ }
+
+ @Test
+ public void testAttentionChangedTriggersCondition() throws RemoteException {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ callbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<IVisualQueryDetectionAttentionListener> listenerCaptor =
+ ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
+ verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
+
+ listenerCaptor.getValue().onAttentionGained();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
+
+ listenerCaptor.getValue().onAttentionLost();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
+
+ verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
index a3740d8..925c06f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.media.AudioManager
-import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserFileManager
@@ -27,10 +26,12 @@
import com.android.systemui.util.RingerModeTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -57,13 +58,15 @@
@Mock
private lateinit var userFileManager: UserFileManager
+ private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
whenever(userTracker.userContext).thenReturn(context)
whenever(userFileManager.getSharedPreferences(any(), any(), any()))
@@ -74,7 +77,10 @@
userTracker,
userFileManager,
ringerModeTracker,
- audioManager
+ audioManager,
+ testScope.backgroundScope,
+ testDispatcher,
+ testDispatcher,
)
}
@@ -103,17 +109,16 @@
}
@Test
- fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() {
+ fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() = testScope.runTest {
//given
val ringerModeCapture = argumentCaptor<Int>()
- val ringerModeInternal = mock<LiveData<Int>>()
- whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
underTest.onTriggered(null)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
//when
val result = underTest.onTriggered(null)
+ runCurrent()
verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
//then
@@ -122,15 +127,14 @@
}
@Test
- fun `triggered - state is not SILENT - move to SILENT ringer`() {
+ fun `triggered - state is not SILENT - move to SILENT ringer`() = testScope.runTest {
//given
val ringerModeCapture = argumentCaptor<Int>()
- val ringerModeInternal = mock<LiveData<Int>>()
- whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
//when
val result = underTest.onTriggered(null)
+ runCurrent()
verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
//then
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 26601b6..34f3ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -37,6 +37,8 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -67,6 +69,7 @@
@Mock
private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
+ private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
private lateinit var underTest: MuteQuickAffordanceCoreStartable
@@ -83,7 +86,8 @@
val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
- testScope = TestScope()
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
underTest = MuteQuickAffordanceCoreStartable(
featureFlags,
@@ -91,7 +95,8 @@
ringerModeTracker,
userFileManager,
keyguardQuickAffordanceRepository,
- testScope,
+ testScope.backgroundScope,
+ testDispatcher,
)
}
@@ -158,6 +163,7 @@
runCurrent()
verify(ringerModeInternal).observeForever(observerCaptor.capture())
observerCaptor.value.onChanged(newRingerMode)
+ runCurrent()
val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
//then
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 4aac7d0..ddd1049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -48,12 +47,11 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
-class BiometricRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: BiometricRepository
+class BiometricSettingsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: BiometricSettingsRepository
@Mock private lateinit var authController: AuthController
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -73,11 +71,11 @@
userRepository = FakeUserRepository()
}
- private suspend fun createBiometricRepository() {
+ private suspend fun createBiometricSettingsRepository() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
userRepository.setSelectedUserInfo(PRIMARY_USER)
underTest =
- BiometricRepositoryImpl(
+ BiometricSettingsRepositoryImpl(
context = context,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
@@ -93,7 +91,7 @@
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
runCurrent()
@@ -119,7 +117,7 @@
@Test
fun strongBiometricAllowedChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
runCurrent()
@@ -142,7 +140,7 @@
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy =
collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 28974f8..8caf60f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -22,7 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
@@ -47,7 +47,7 @@
class AlternateBouncerInteractorTest : SysuiTestCase() {
private lateinit var underTest: AlternateBouncerInteractor
private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var biometricRepository: FakeBiometricRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var deviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
@Mock private lateinit var systemClock: SystemClock
@@ -65,13 +65,13 @@
TestCoroutineScope(),
bouncerLogger,
)
- biometricRepository = FakeBiometricRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
bouncerRepository,
- biometricRepository,
+ biometricSettingsRepository,
deviceEntryFingerprintAuthRepository,
systemClock,
keyguardUpdateMonitor,
@@ -96,7 +96,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -104,7 +104,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
- biometricRepository.setStrongBiometricAllowed(false)
+ biometricSettingsRepository.setStrongBiometricAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -112,7 +112,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnabledByDevicePolicy(false)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -159,13 +159,13 @@
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
- biometricRepository.setFingerprintEnrolled(true)
- biometricRepository.setStrongBiometricAllowed(true)
- biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+ biometricSettingsRepository.setFingerprintEnrolled(true)
+ biometricSettingsRepository.setStrongBiometricAllowed(true)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
}
private fun givenCannotShowAlternateBouncer() {
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
index 1d6e980..670f117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
@@ -113,5 +113,6 @@
recommendations = emptyList(),
dismissIntent = null,
headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1)
+ instanceId = InstanceId.fakeInstanceId(-1),
+ expiryTimeMs = 0,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 9d33e6f..eb6235c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -27,11 +27,13 @@
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.util.MediaFlags
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
@@ -40,11 +42,11 @@
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.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
private const val KEY = "TEST_KEY"
@@ -72,6 +74,7 @@
@Mock private lateinit var smartspaceData: SmartspaceMediaData
@Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
@Mock private lateinit var logger: MediaUiEventLogger
+ @Mock private lateinit var mediaFlags: MediaFlags
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
@@ -82,6 +85,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
MediaPlayerData.clear()
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
mediaDataFilter =
MediaDataFilter(
context,
@@ -90,7 +94,8 @@
lockscreenUserManager,
executor,
clock,
- logger
+ logger,
+ mediaFlags
)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
@@ -108,19 +113,20 @@
)
dataGuest = dataMain.copy(userId = USER_GUEST)
- `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
- `when`(smartspaceData.isActive).thenReturn(true)
- `when`(smartspaceData.isValid()).thenReturn(true)
- `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
- `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
- `when`(smartspaceData.headphoneConnectionTimeMillis)
+ whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
+ whenever(smartspaceData.isActive).thenReturn(true)
+ whenever(smartspaceData.isValid()).thenReturn(true)
+ whenever(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
+ whenever(smartspaceData.recommendations)
+ .thenReturn(listOf(smartspaceMediaRecommendationItem))
+ whenever(smartspaceData.headphoneConnectionTimeMillis)
.thenReturn(clock.currentTimeMillis() - 100)
- `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
+ whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
}
private fun setUser(id: Int) {
- `when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
- `when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
+ whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
mediaDataFilter.handleUserSwitched(id)
}
@@ -277,7 +283,7 @@
@Test
fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -285,7 +291,7 @@
@Test
fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() {
- `when`(smartspaceData.isValid()).thenReturn(false)
+ whenever(smartspaceData.isValid()).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -293,8 +299,8 @@
@Test
fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() {
- `when`(smartspaceData.isActive).thenReturn(true)
- `when`(smartspaceData.isValid()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(true)
+ whenever(smartspaceData.isValid()).thenReturn(true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
@@ -349,7 +355,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -379,7 +385,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
@@ -395,7 +401,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -418,7 +424,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() {
- `when`(smartspaceData.isValid()).thenReturn(false)
+ whenever(smartspaceData.isValid()).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -513,4 +519,59 @@
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
+
+ @Test
+ fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(false)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+ }
+
+ @Test
+ fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(false)
+
+ // If there is media that was recently played but inactive
+ val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
+
+ // And an inactive recommendation is loaded
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ // Smartspace is loaded but the media stays inactive
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+ }
+
+ @Test
+ fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ val data =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = SMARTSPACE_KEY,
+ isActive = true,
+ packageName = SMARTSPACE_PACKAGE,
+ recommendations = listOf(smartspaceMediaRecommendationItem),
+ )
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
+ mediaDataFilter.onSwipeToDismiss()
+
+ verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
+ verify(mediaDataManager, never())
+ .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 53cc78f..44e2fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -46,6 +46,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
@@ -82,6 +84,8 @@
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+private const val SMARTSPACE_CREATION_TIME = 1234L
+private const val SMARTSPACE_EXPIRY_TIME = 5678L
private const val PACKAGE_NAME = "com.example.app"
private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
@@ -230,10 +234,12 @@
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
- whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+ whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
+ whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
}
@@ -847,8 +853,9 @@
cardAction = mediaSmartspaceBaseAction,
recommendations = validRecommendationList,
dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -870,8 +877,9 @@
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -901,8 +909,9 @@
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
dismissIntent = null,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -931,6 +940,129 @@
}
@Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val extras =
+ Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf())
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
+ }
+
+ @Test
+ fun testSetRecommendationInactive_notifiesListeners() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
// WHEN media recommendation setting is off
Settings.Secure.putInt(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
index 92bf84c..8baa06a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
@@ -25,13 +25,16 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
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
@@ -48,7 +51,6 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY"
@@ -56,6 +58,7 @@
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -85,10 +88,13 @@
private lateinit var resumeData: MediaData
private lateinit var mediaTimeoutListener: MediaTimeoutListener
private var clock = FakeSystemClock()
+ @Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var smartspaceData: SmartspaceMediaData
@Before
fun setup() {
- `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
+ whenever(mediaControllerFactory.create(any())).thenReturn(mediaController)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
executor = FakeExecutor(clock)
mediaTimeoutListener =
MediaTimeoutListener(
@@ -96,7 +102,8 @@
executor,
logger,
statusBarStateController,
- clock
+ clock,
+ mediaFlags,
)
mediaTimeoutListener.timeoutCallback = timeoutCallback
mediaTimeoutListener.stateCallback = stateCallback
@@ -133,9 +140,9 @@
@Test
fun testOnMediaDataLoaded_registersPlaybackListener() {
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
verify(logger).logPlaybackState(eq(KEY), eq(playingState))
@@ -188,8 +195,8 @@
// To playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
verify(mediaController).unregisterCallback(anyObject())
verify(mediaController).registerCallback(anyObject())
@@ -208,8 +215,8 @@
// Migrate, still not playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
// The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
@@ -296,8 +303,8 @@
// WHEN we get an update with media playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(mediaController.playbackState).thenReturn(playingState)
val mediaPlaying = mediaData.copy(isPlaying = true)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPlaying)
@@ -347,7 +354,7 @@
// WHEN regular media is paused
val pausedState =
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
- `when`(mediaController.playbackState).thenReturn(pausedState)
+ whenever(mediaController.playbackState).thenReturn(pausedState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
@@ -379,7 +386,7 @@
// AND that media is resumed
val playingState =
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
// THEN the timeout length is changed to a regular media control
@@ -593,8 +600,91 @@
assertThat(executor.numPending()).isEqualTo(1)
}
+ @Test
+ fun testSmartspaceDataLoaded_schedulesTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val duration = 60_000
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(duration)
+ }
+
+ @Test
+ fun testSmartspaceMediaData_timesOut_invokesCallback() {
+ // Given a pending timeout
+ testSmartspaceDataLoaded_schedulesTimeout()
+
+ executor.runAllReady()
+ verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+ }
+
+ @Test
+ fun testSmartspaceDataLoaded_alreadyExists_updatesTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val duration = 100
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ val expiryLonger = expireTime + duration
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expiryLonger)
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(duration * 2)
+ }
+
+ @Test
+ fun testSmartspaceDataRemoved_cancelTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ mediaTimeoutListener.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testSmartspaceData_dozedPastTimeout_invokedOnWakeup() {
+ // Given a pending timeout
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
+ val duration = 60_000
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // And we doze past the scheduled timeout
+ val time = clock.currentTimeMillis()
+ clock.setElapsedRealtime(time + duration * 2)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // Then when no longer dozing, the timeout runs immediately
+ dozingCallbackCaptor.value.onDozingChanged(false)
+ verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+ verify(logger).logTimeout(eq(SMARTSPACE_KEY))
+
+ // and cancel any later scheduled timeout
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
private fun loadMediaDataWithPlaybackState(state: PlaybackState) {
- `when`(mediaController.playbackState).thenReturn(state)
+ whenever(mediaController.playbackState).thenReturn(state)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5e5dc8b..e201b6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.util.time.FakeSystemClock
import javax.inject.Provider
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
@@ -126,6 +127,7 @@
whenever(mediaControlPanelFactory.get()).thenReturn(panel)
whenever(panel.mediaViewController).thenReturn(mediaViewController)
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
}
@@ -703,4 +705,39 @@
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
)
}
+
+ @Test
+ fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+ testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
+
+ // When an update to existing smartspace data is loaded
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+ true
+ )
+
+ // Then the carousel is updated
+ assertTrue(MediaPlayerData.playerKeys().elementAt(0).data.active)
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+ }
+
+ @Test
+ fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ // When inactive smartspace data is loaded
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA,
+ false
+ )
+
+ // Then it is added to the carousel with correct state
+ assertTrue(MediaPlayerData.playerKeys().elementAt(0).isSsMediaRec)
+ assertFalse(MediaPlayerData.playerKeys().elementAt(0).data.active)
+
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec)
+ assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 26b9204..55a33b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -52,6 +52,7 @@
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
+import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.widget.CachingIconView
@@ -206,6 +207,12 @@
@Mock private lateinit var coverContainer3: ViewGroup
@Mock private lateinit var recAppIconItem: CachingIconView
@Mock private lateinit var recCardTitle: TextView
+ @Mock private lateinit var recProgressBar1: SeekBar
+ @Mock private lateinit var recProgressBar2: SeekBar
+ @Mock private lateinit var recProgressBar3: SeekBar
+ @Mock private lateinit var recSubtitleMock1: TextView
+ @Mock private lateinit var recSubtitleMock2: TextView
+ @Mock private lateinit var recSubtitleMock3: TextView
@Mock private lateinit var coverItem: ImageView
private lateinit var coverItem1: ImageView
private lateinit var coverItem2: ImageView
@@ -2081,6 +2088,10 @@
whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
whenever(recommendationViewHolder.mediaCoverItems)
.thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
@@ -2119,6 +2130,65 @@
}
@Test
+ fun bindRecommendationWithProgressBars() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
+
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(Color.RED)
+ val albumArt = Icon.createWithBitmap(bmp)
+ val bundle =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
+ }
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(bundle)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ verify(recProgressBar1).setProgress(50)
+ verify(recProgressBar1).visibility = View.VISIBLE
+ verify(recProgressBar2).visibility = View.GONE
+ verify(recProgressBar3).visibility = View.GONE
+ verify(recSubtitleMock1).visibility = View.GONE
+ verify(recSubtitleMock2).visibility = View.VISIBLE
+ verify(recSubtitleMock3).visibility = View.VISIBLE
+ }
+
+ @Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index ef10e40..db890f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -624,7 +624,7 @@
}
@Test
- fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+ fun commandQueueCallback_receiverSucceededThenThisDeviceSucceeded_invalidTransitionLogged() {
displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
@@ -634,7 +634,7 @@
reset(windowManager)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
null
)
@@ -644,7 +644,7 @@
}
@Test
- fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+ fun commandQueueCallback_thisDeviceSucceededThenReceiverSucceeded_invalidTransitionLogged() {
displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
@@ -654,7 +654,7 @@
reset(windowManager)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
null
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
index 9de55b1..1ddfc4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
@@ -34,6 +34,22 @@
public final class SchemeContentTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeContent scheme = new SchemeContent(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff656DD3);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff81009F);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767684);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff757589);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF1218FF);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
index 3ef9280..31e8711 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
@@ -34,158 +34,174 @@
public final class SchemeExpressiveTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff35855F);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff8C6D8C);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff806EA1);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff79757F);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff7A7585);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff32835D);
}
@Test
public void lightTheme_standardContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff924b28);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff146C48);
}
@Test
public void lightTheme_maxContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff401400);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff002818);
}
@Test
public void lightTheme_minContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffA2F4C6);
}
@Test
public void lightTheme_standardContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffA2F4C6);
}
@Test
public void lightTheme_maxContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff6f3010);
+ 0xff004D31);
}
@Test
public void lightTheme_minContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff99512e);
+ 0xff1e724e);
}
@Test
public void lightTheme_standardContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff351000);
+ 0xff002112);
}
@Test
public void lightTheme_maxContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffd0bc);
+ 0xff9aebbe);
}
@Test
public void lightTheme_minContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void lightTheme_standardContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void lightTheme_maxContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void darkTheme_minContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff32835d);
}
@Test
public void darkTheme_standardContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffffb595);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff87d7ab);
}
@Test
public void darkTheme_maxContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfffff3ee);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffd5ffe4);
}
@Test
public void darkTheme_minContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff743413);
+ 0xff005234);
}
@Test
public void darkTheme_standardContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff743413);
+ 0xff005234);
}
@Test
public void darkTheme_maxContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffbb9e);
+ 0xff8bdbaf);
}
@Test
public void darkTheme_minContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xfff99f75);
+ 0xff76c59b);
}
@Test
public void darkTheme_standardContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffa2f4c6);
}
@Test
public void darkTheme_maxContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff622706);
+ 0xff004229);
}
@Test
public void darkTheme_minContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
index cfd0518..511f83b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
@@ -32,6 +32,21 @@
@SmallTest
@RunWith(JUnit4.class)
public final class SchemeFidelityTest extends SysuiTestCase {
+ @Test
+ public void testKeyColors() {
+ SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff656DD3);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff9D0002);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767684);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff757589);
+ }
@Test
public void lightTheme_minContrast_primary() {
@@ -251,5 +266,4 @@
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12121d);
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
index ce6f6d9..4cf14d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
@@ -34,6 +34,22 @@
public final class SchemeFruitSaladTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff0091C0);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff3A7E9E);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff6E72AC);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777682);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff007ea7);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
index 9ffc523..3eca4dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
@@ -34,6 +34,22 @@
public final class SchemeMonochromeTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff747474);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
index c6892f9..71e9f4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
@@ -34,6 +34,22 @@
public final class SchemeNeutralTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767685);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777680);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff787678);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff787678);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff737383);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
index a083622..cc6f044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
@@ -34,6 +34,22 @@
public final class SchemeRainbowTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff696FC4);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff936B84);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff676DC1);
@@ -230,5 +246,4 @@
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
index b0c7f10..e4880d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
@@ -34,21 +34,37 @@
public final class SchemeTonalSpotTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff6E72AC);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff936B84);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff78767A);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777680);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6c70aa);
}
@Test
public void lightTheme_standardContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff545999);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff555992);
}
@Test
public void lightTheme_maxContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff161a59);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff181c51);
}
@Test
@@ -69,21 +85,21 @@
public void lightTheme_maxContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff383c7c);
+ 0xff3a3e74);
}
@Test
public void lightTheme_minContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff5a5fa0);
+ 0xff5C5F98);
}
@Test
public void lightTheme_standardContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff0e1253);
+ 0xff11144B);
}
@Test
@@ -96,37 +112,37 @@
@Test
public void lightTheme_minContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_standardContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_maxContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_minContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff5f5e65);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff605E62);
}
@Test
public void lightTheme_standardContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1b1b21);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1B1B1F);
}
@Test
public void lightTheme_maxContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1a1a20);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1B1A1E);
}
@Test
@@ -186,7 +202,7 @@
@Test
public void darkTheme_minContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6C70AA);
}
@Test
@@ -205,14 +221,14 @@
public void darkTheme_minContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff3c4180);
+ 0xff3E4278);
}
@Test
public void darkTheme_standardContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff3c4180);
+ 0xff3E4278);
}
@Test
@@ -226,7 +242,7 @@
public void darkTheme_minContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffabaff7);
+ 0xffadb0ef);
}
@Test
@@ -240,7 +256,7 @@
public void darkTheme_maxContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff2e3271);
+ 0xff30346A);
}
@Test
@@ -321,36 +337,36 @@
@Test
public void darkTheme_minContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_minContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffa4a2a9);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffa4a2a6);
}
@Test
public void darkTheme_standardContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe4e1e9);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe5e1e6);
}
@Test
public void darkTheme_maxContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe5e2ea);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe6e2e7);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
index f056a16..e5963a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
@@ -34,6 +34,22 @@
public final class SchemeVibrantTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff7B7296);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff886C9D);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777682);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767685);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5660ff);
@@ -195,18 +211,18 @@
@Test
public void darkTheme_minContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 3da7a22..8019fb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,7 +42,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -158,16 +157,6 @@
}
@Test
- public void testDisableLowBatteryReminder_noNotification() {
- mGlobalSettings.putInt(Settings.Global.LOW_POWER_MODE_REMINDER_ENABLED, 0);
-
- mPowerNotificationWarnings.showLowBatteryWarning(false);
-
- verify(mMockNotificationManager, times(0))
- .notifyAsUser(anyString(), eq(SystemMessage.NOTE_POWER_LOW), any(), any());
- }
-
- @Test
public void testShowLowBatteryNotification_NotifyAsUser() {
mPowerNotificationWarnings.showLowBatteryWarning(false);
verify(mMockNotificationManager, times(1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 80c39cf..addca9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -37,10 +37,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -135,4 +137,24 @@
assertThat(mTile.getState().secondaryLabel)
.isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
}
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_after_wifi_disconnected() {
+ WifiIndicators wifiIndicators = new WifiIndicators(
+ /* enabled= */ true,
+ /* statusIcon= */ null,
+ /* qsIcon= */ null,
+ /* activityIn= */ false,
+ /* activityOut= */ false,
+ /* description= */ null,
+ /* isTransient= */ false,
+ /* statusLabel= */ null
+ );
+ mTile.mSignalCallback.setWifiIndicators(wifiIndicators);
+ IconState state = new IconState(true, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().icon).isEqualTo(
+ QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 541d6c2..2e73c0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,7 +23,7 @@
import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.os.UserHandle
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -35,6 +35,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert
import org.junit.Test
private const val USER_ID = 1
@@ -55,7 +56,7 @@
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotRequest? = null
@@ -78,8 +79,10 @@
fun testProcessAsync_ScreenshotData() {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotData.fromRequest(
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build())
+ val request =
+ ScreenshotData.fromRequest(
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+ )
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotData? = null
@@ -102,7 +105,7 @@
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -162,7 +165,7 @@
)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -191,6 +194,32 @@
}
@Test
+ fun testFullScreenshot_managedProfile_nullBitmap() {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Provide a null task bitmap when asked
+ imageCapture.image = null
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ Assert.assertThrows(IllegalStateException::class.java) {
+ runBlocking { processor.process(request) }
+ }
+ Assert.assertThrows(IllegalStateException::class.java) {
+ runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
+ }
+ }
+
+ @Test
fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
new file mode 100644
index 0000000..1f18d91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ScreenshotDetectionControllerTest {
+
+ @Mock lateinit var windowManager: IWindowManager
+
+ @Mock lateinit var packageManager: PackageManager
+
+ lateinit var controller: ScreenshotDetectionController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = ScreenshotDetectionController(windowManager, packageManager)
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_ignoresOverview() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertTrue(list.isEmpty())
+ verify(windowManager, never()).notifyScreenshotListeners(any())
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_emptySet() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf())
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertTrue(list.isEmpty())
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_oneApp() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ val component = ComponentName("package1", "class1")
+ val appName = "app name"
+ val activityInfo = mock(ActivityInfo::class.java)
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo)
+ whenever(activityInfo.loadLabel(eq(packageManager))).thenReturn(appName)
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf(component))
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertEquals(1, list.size)
+ assertEquals(appName, list[0])
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_multipleApps() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ val component1 = ComponentName("package1", "class1")
+ val component2 = ComponentName("package2", "class2")
+ val component3 = ComponentName("package3", "class3")
+ val appName1 = "app name 1"
+ val appName2 = "app name 2"
+ val appName3 = "app name 3"
+
+ val activityInfo1 = mock(ActivityInfo::class.java)
+ val activityInfo2 = mock(ActivityInfo::class.java)
+ val activityInfo3 = mock(ActivityInfo::class.java)
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component1),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo1)
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component2),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo2)
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component3),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo3)
+
+ whenever(activityInfo1.loadLabel(eq(packageManager))).thenReturn(appName1)
+ whenever(activityInfo2.loadLabel(eq(packageManager))).thenReturn(appName2)
+ whenever(activityInfo3.loadLabel(eq(packageManager))).thenReturn(appName3)
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf(component1, component2, component3))
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertEquals(3, list.size)
+ assertEquals(appName1, list[0])
+ assertEquals(appName2, list[1])
+ assertEquals(appName3, list[2])
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 74969d0..1fa2ace 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -38,8 +38,9 @@
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
+import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
@@ -47,6 +48,7 @@
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -55,10 +57,10 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doThrow
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
private const val USER_ID = 1
private const val TASK_ID = 11
@@ -107,18 +109,20 @@
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(
- /* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
+ val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+ val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .whenever(requestProcessor)
+ .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
doAnswer {
- val request: ScreenshotData = it.getArgument(0) as ScreenshotData
- val consumer: Consumer<ScreenshotData> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(
- /* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
+ val request: ScreenshotData = it.getArgument(0) as ScreenshotData
+ val consumer: Consumer<ScreenshotData> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .whenever(requestProcessor)
+ .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -162,37 +166,52 @@
/* requestCallback = */ any()
)
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+ assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_OTHER.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotFullscreen_screenshotDataEnabled() {
flags.set(SCREENSHOT_METADATA, true)
- val request = ScreenshotRequest.Builder(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_OTHER).setTopComponent(topComponent).build()
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
- verify(controller, times(1)).handleScreenshot(
- eq(ScreenshotData.fromRequest(request)),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
+ verify(controller, times(1))
+ .handleScreenshot(
+ eq(ScreenshotData.fromRequest(request)),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_OTHER.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
@@ -224,7 +243,7 @@
/* requestCallback = */ any()
)
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+ assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
val logEvent = eventLogger.get(0)
assertEquals(
@@ -241,6 +260,8 @@
@Test
fun takeScreenshotFullscreen_userLocked() {
+ flags.set(SCREENSHOT_METADATA, true)
+
whenever(userManager.isUserUnlocked).thenReturn(false)
val request =
@@ -253,10 +274,36 @@
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
verify(callback, times(1)).reportError()
verifyZeroInteractions(controller)
+
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
}
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
+ flags.set(SCREENSHOT_METADATA, true)
+
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
.thenReturn(true)
@@ -279,6 +326,206 @@
// error shown: Toast.makeText(...).show(), untestable
verify(callback, times(1)).reportError()
verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+ whenever(userManager.isUserUnlocked).thenReturn(false)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verify(callback, times(1)).reportError()
+ verifyZeroInteractions(controller)
+
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+
+ whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+ .thenReturn(true)
+
+ whenever(
+ devicePolicyResourcesManager.getString(
+ eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+ /* Supplier<String> */
+ any(),
+ )
+ )
+ .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ // error shown: Toast.makeText(...).show(), untestable
+ verify(callback, times(1)).reportError()
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ doThrow(IllegalStateException::class.java)
+ .whenever(requestProcessor)
+ .processAsync(any(ScreenshotRequest::class.java), any())
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(callback, times(1)).reportError()
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+ @Test
+ fun takeScreenshot_workProfile_nullBitmap() {
+ flags.set(SCREENSHOT_METADATA, true)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ doThrow(IllegalStateException::class.java)
+ .whenever(requestProcessor)
+ .processAsync(any(ScreenshotData::class.java), any())
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(callback, times(1)).reportError()
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 4d7741ad..78bebb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
@@ -59,7 +60,7 @@
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
- private var settingValue: String = ""
+ private var settingValue: ClockSettings? = null
companion object {
private fun failFactory(): ClockController {
@@ -79,7 +80,8 @@
private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
override fun getClocks() = metadata
- override fun createClock(id: ClockId): ClockController = createCallbacks[id]!!()
+ override fun createClock(settings: ClockSettings): ClockController =
+ createCallbacks[settings.clockId!!]!!()
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
fun addClock(
@@ -110,7 +112,7 @@
userHandle = UserHandle.USER_ALL,
defaultClockProvider = fakeDefaultProvider
) {
- override var currentClockId: ClockId
+ override var settings: ClockSettings?
get() = settingValue
set(value) { settingValue = value }
}
@@ -185,7 +187,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
@@ -203,7 +205,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3")
.addClock("clock_4", "clock 4")
@@ -222,7 +224,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
@@ -242,8 +244,8 @@
@Test
fun jsonDeserialization_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", 500)
- val actual = ClockRegistry.ClockSetting.deserialize("""{
+ val expected = ClockSettings("ID", null, 500)
+ val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":500
}""")
@@ -252,15 +254,15 @@
@Test
fun jsonDeserialization_noTimestamp_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", null)
- val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+ val expected = ClockSettings("ID", null, null)
+ val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
assertEquals(expected, actual)
}
@Test
fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", null)
- val actual = ClockRegistry.ClockSetting.deserialize("""{
+ val expected = ClockSettings("ID", null, null)
+ val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":null
}""")
@@ -269,22 +271,22 @@
@Test(expected = JSONException::class)
fun jsonDeserialization_noId_threwException() {
- val expected = ClockRegistry.ClockSetting("ID", 500)
- val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+ val expected = ClockSettings("ID", null, 500)
+ val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
assertEquals(expected, actual)
}
@Test
fun jsonSerialization_gotExpectedString() {
val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
- val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null, 500))
assertEquals(expected, actual)
}
@Test
fun jsonSerialization_noTimestamp_gotExpectedString() {
val expected = "{\"clockId\":\"ID\"}"
- val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null, null))
assertEquals(expected, actual)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index a7588dd..cd2efc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -114,7 +114,8 @@
@Test
fun defaultClock_events_onTimeTick() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onTimeTick()
+ clock.smallClock.events.onTimeTick()
+ clock.largeClock.events.onTimeTick()
verify(mockSmallClockView).refreshTime()
verify(mockLargeClockView).refreshTime()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index af52459..701cf95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -117,6 +117,10 @@
extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
mNotification.extras = extras;
+ // GIVEN notification has the permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// THEN don't filter out the notification
assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
}
@@ -126,10 +130,15 @@
// GIVEN device is unprovisioned
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
- // GIVEN notification does not have the flag to allow the notification during setup
+ // GIVEN notification has a flag to allow the notification during setup
Bundle extras = new Bundle();
+ extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
mNotification.extras = extras;
+ // GIVEN notification does NOT have permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
// THEN filter out the notification
assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 1b62d5c..bd24922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -77,6 +78,7 @@
MobileIconsInteractorImpl(
connectionsRepository,
carrierConfigTracker,
+ logger = mock(),
userSetupRepository,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index a24e29ae..b91a4df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
@@ -122,6 +124,39 @@
}
@Test
+ fun contentDescription_notInService_usesNoPhone() =
+ testScope.runTest {
+ var latest: ContentDescription? = null
+ val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+ interactor.isInService.value = false
+
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE)
+
+ job.cancel()
+ }
+
+ @Test
+ fun contentDescription_inService_usesLevel() =
+ testScope.runTest {
+ var latest: ContentDescription? = null
+ val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+ interactor.isInService.value = true
+
+ interactor.level.value = 2
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH[2])
+
+ interactor.level.value = 0
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH[0])
+
+ job.cancel()
+ }
+
+ @Test
fun networkType_dataEnabled_groupIsRepresented() =
testScope.runTest {
val expected =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 034c618..ccf378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,12 +17,17 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
@@ -39,7 +44,14 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -48,6 +60,8 @@
class UserRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: IActivityManager
+ @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
private lateinit var underTest: UserRepositoryImpl
@@ -214,6 +228,34 @@
assertThat(selectedUserInfo?.id).isEqualTo(1)
}
+ @Test
+ fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+ underTest = create(this)
+
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+
+ verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+ }
+
+ @Test
+ fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+ underTest = create(this)
+ verify(activityManager)
+ .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+
+ userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+
+ var mostRecentSwitchingValue = false
+ underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
+
+ assertThat(mostRecentSwitchingValue).isTrue()
+
+ userSwitchObserver.value.onUserSwitchComplete(0)
+ assertThat(mostRecentSwitchingValue).isFalse()
+ }
+
private fun createUserInfo(
id: Int,
isGuest: Boolean,
@@ -280,6 +322,8 @@
}
private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(FACE_AUTH_REFACTOR, true)
return UserRepositoryImpl(
appContext = context,
manager = manager,
@@ -288,6 +332,8 @@
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
+ activityManager = activityManager,
+ featureFlags = featureFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
similarity index 95%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index f3e52de..044679d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeBiometricRepository : BiometricRepository {
+class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
index 6ae7c34..1403cea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -21,7 +21,7 @@
import android.view.Display
import java.util.concurrent.Executor
-class FakeDisplayTracker internal constructor(val context: Context) : DisplayTracker {
+class FakeDisplayTracker constructor(val context: Context) : DisplayTracker {
val displayManager: DisplayManager = context.getSystemService(DisplayManager::class.java)!!
override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
override var allDisplays: Array<Display> = displayManager.displays
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ea5a302..1a8e244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,6 +39,10 @@
private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+ private val _userSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _userSwitchingInProgress
+
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private var _isGuestUserAutoCreated: Boolean = false
diff --git a/services/api/current.txt b/services/api/current.txt
index f7d6ee9..70ee3b8 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -225,6 +225,14 @@
}
+package com.android.server.security {
+
+ public final class FileIntegrityLocal {
+ method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ }
+
+}
+
package com.android.server.stats {
public final class StatsHelper {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f0b168d..ce6a6c8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -175,6 +175,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.VisualQueryDetectionService;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -3808,6 +3810,17 @@
inSharedIsolatedProcess);
}
+ // TODO(b/265746493): Special case for HotwordDetectionService and
+ // VisualQueryDetectionService. Need a cleaner way to append this seInfo.
+ private String generateAdditionalSeInfoFromService(Intent service) {
+ if (service != null && service.getAction() != null
+ && (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
+ || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE))) {
+ return ":isolatedComputeApp";
+ }
+ return "";
+ }
+
private ServiceLookupResult retrieveServiceLocked(Intent service,
String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
String sdkSandboxClientAppPackage, String resolvedType,
@@ -3925,6 +3938,7 @@
r.mRecentCallingPackage = callingPackage;
r.mRecentCallingUid = callingUid;
}
+ r.appInfo.seInfo += generateAdditionalSeInfoFromService(service);
return new ServiceLookupResult(r, resolution.getAlias());
}
@@ -4150,6 +4164,7 @@
return null;
}
}
+ r.appInfo.seInfo += generateAdditionalSeInfoFromService(service);
return new ServiceLookupResult(r, resolution.getAlias());
}
return null;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index b1a01cc..7290f32 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -273,7 +273,8 @@
performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.userId, oldRecord.callingUid, r.callingUid,
+ false, false, r.shareIdentity, oldRecord.userId,
+ oldRecord.callingUid, r.callingUid, r.callerPackage,
SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
@@ -402,7 +403,9 @@
prepareReceiverIntent(r.intent, r.curFilteredExtras),
r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, app.mState.getReportedProcState()));
+ r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState()));
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
@@ -726,9 +729,15 @@
public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
- boolean ordered, boolean sticky, int sendingUser,
- int receiverUid, int callingUid, long dispatchDelay,
- long receiveDelay) throws RemoteException {
+ boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
+ int receiverUid, int callingUid, String callingPackage,
+ long dispatchDelay, long receiveDelay) throws RemoteException {
+ // If the broadcaster opted-in to sharing their identity, then expose package visibility for
+ // the receiver.
+ if (shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
+ UserHandle.getAppId(receiverUid), callingUid, true);
+ }
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null) {
final IApplicationThread thread = app.getThread();
@@ -740,6 +749,8 @@
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
receiver, intent, resultCode,
data, extras, ordered, sticky, assumeDelivered, sendingUser,
+ shareIdentity ? callingUid : Process.INVALID_UID,
+ shareIdentity ? callingPackage : null,
app.mState.getReportedProcState()));
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
@@ -845,8 +856,8 @@
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.userId,
- filter.receiverList.uid, r.callingUid,
+ r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
+ filter.receiverList.uid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
r.receiverTime - r.dispatchTime);
// parallel broadcasts are fire-and-forget, not bookended by a call to
@@ -1130,8 +1141,8 @@
r.mIsReceiverAppRunning = true;
performReceiveLocked(r.resultToApp, r.resultTo,
new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false, r.userId,
- r.callingUid, r.callingUid,
+ r.resultData, r.resultExtras, false, false, r.shareIdentity,
+ r.userId, r.callingUid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
now - r.dispatchTime);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 99e2ac7..b952ce0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -893,6 +893,8 @@
batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
r.ordered, r.initialSticky, assumeDelivered, r.userId,
+ r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
app.mState.getReportedProcState(), r, index);
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
@@ -903,7 +905,9 @@
} else {
batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, app.mState.getReportedProcState(), r, index);
+ r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState(), r, index);
if (assumeDelivered) {
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
return true;
@@ -946,6 +950,11 @@
final BroadcastRecord r = cookie.r;
final int index = cookie.index;
final Object receiver = r.receivers.get(index);
+
+ if (r.shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+ UserHandle.getAppId(app.uid), r.callingUid, true);
+ }
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(queue.app, r, (BroadcastFilter) receiver);
} else {
@@ -1055,12 +1064,19 @@
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
+ if (r.shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+ UserHandle.getAppId(app.uid), r.callingUid, true);
+ }
try {
final boolean assumeDelivered = true;
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
r.resultTo, r.intent,
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
- assumeDelivered, r.userId, app.mState.getReportedProcState()));
+ assumeDelivered, r.userId,
+ r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState()));
} catch (RemoteException e) {
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
logw(msg);
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index 226647c..153403a 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -171,8 +171,8 @@
// Add a ReceiverInfo for a registered receiver.
void schedule(@Nullable IIntentReceiver receiver, Intent intent,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
- @Nullable BroadcastRecord r, int index) {
+ boolean sticky, boolean assumeDelivered, int sendingUser, int callingUid,
+ String callingPackage, int processState, @Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
@@ -185,6 +185,9 @@
ri.receiver = receiver;
ri.ordered = ordered;
ri.sticky = sticky;
+ ri.sentFromUid = callingUid;
+ ri.sentFromPackage = callingPackage;
+
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
}
@@ -192,7 +195,8 @@
void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
@Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
- int processState, @Nullable BroadcastRecord r, int index) {
+ int callingUid, @Nullable String callingPackage, int processState,
+ @Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
@@ -205,6 +209,8 @@
ri.activityInfo = activityInfo;
ri.compatInfo = compatInfo;
ri.sync = sync;
+ ri.sentFromUid = callingUid;
+ ri.sentFromPackage = callingPackage;
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
}
@@ -217,20 +223,21 @@
ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
@Nullable Intent intent, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int callingUid, String callingPackage, int processState) {
reset();
schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
- sendingUser, processState, null, 0);
+ sendingUser, callingUid, callingPackage, processState, null, 0);
return receivers();
}
ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
@Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
- boolean assumeDelivered, int sendingUser, int processState) {
+ boolean assumeDelivered, int sendingUser, int callingUid, String callingPackage,
+ int processState) {
reset();
schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
- sendingUser, processState, null, 0);
+ sendingUser, callingUid, callingPackage, processState, null, 0);
return receivers();
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 4304377..6035ad9 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -89,6 +89,7 @@
final boolean initialSticky; // initial broadcast from register to sticky?
final boolean prioritized; // contains more than one priority tranche
final boolean deferUntilActive; // infinitely deferrable broadcast
+ final boolean shareIdentity; // whether the broadcaster's identity should be shared
final int userId; // user id this broadcast was for
final @Nullable String resolvedType; // the resolved data type
final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -417,6 +418,7 @@
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
interactive = options != null && options.isInteractive();
+ shareIdentity = options != null && options.isShareIdentityEnabled();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
@@ -481,6 +483,7 @@
pushMessage = from.pushMessage;
pushMessageOverQuota = from.pushMessageOverQuota;
interactive = from.interactive;
+ shareIdentity = from.shareIdentity;
filterExtrasForReceiver = from.filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e02dda6..3643db0 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1898,7 +1898,7 @@
// For short FGS.
adjType = "fg-service-short";
// We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
- // (which uses MEDIUM_APP_ADJ + 1)
+ // (which uses MEDIUM_APP_ADJ + 2)
// from short-FGS.
// (We use +1 and +2, not +0 and +1, to be consistent with the following
// RECENT_FOREGROUND_APP_ADJ tweak)
@@ -2319,6 +2319,14 @@
&& adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
newAdj = PERCEPTIBLE_LOW_APP_ADJ;
} else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0
+ && (cr.flags & Context.BIND_NOT_FOREGROUND) == 0
+ && clientAdj < PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+ // This is for user-initiated jobs.
+ // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
+ newAdj = PERCEPTIBLE_APP_ADJ + 1;
+ } else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0
+ && (cr.flags & Context.BIND_NOT_FOREGROUND) != 0
&& clientAdj < PERCEPTIBLE_APP_ADJ
&& adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
// This is for expedited jobs.
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index 082e8e0..dcb02ea 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -48,11 +48,12 @@
@Override
public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
- ordered, assumeDelivered, sendingUser, processState);
+ ordered, assumeDelivered, sendingUser, processState, sentFromUid,
+ sentFromPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -62,11 +63,12 @@
@Override
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
- ordered, sticky, assumeDelivered, sendingUser, processState);
+ ordered, sticky, assumeDelivered, sendingUser, processState, sentFromUid,
+ sentFromPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -80,11 +82,11 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
- r.sendingUser, r.processState);
+ r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
- r.sendingUser, r.processState);
+ r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
}
}
}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35df3ee..c0ea561 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -86,7 +86,8 @@
private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
- ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER);
+ ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, SHORTCUT_MANAGER_HELPER
+ );
private int mUserId = UserHandle.USER_SYSTEM;
@@ -101,7 +102,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
- addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper(mUserId));
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 188672a..a921a54 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -118,11 +118,13 @@
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
+ assert layout != null;
Layout.Display display = layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
d.isEnabled(),
- mIdProducer);
+ mIdProducer,
+ d.getBrightnessThrottlingMapId());
if (FRONT_STRING.equals(d.getPosition())) {
display.setPosition(POSITION_FRONT);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e048a0f..2af995b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -74,8 +74,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -411,6 +413,8 @@
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
+ static final String DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID = "default";
+
private static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
private static final String DISPLAY_CONFIG_DIR = "displayconfig";
@@ -421,6 +425,8 @@
private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
+ private static final int DEFAULT_REFRESH_RATE = 60;
private static final int DEFAULT_LOW_REFRESH_RATE = 60;
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
@@ -589,17 +595,29 @@
* using higher refresh rates, even if display modes with higher refresh rates are available
* from hardware composer. Only has an effect if the value is non-zero.
*/
- private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+ private int mDefaultPeakRefreshRate = DEFAULT_PEAK_REFRESH_RATE;
/**
* The default refresh rate for a given device. This value sets the higher default
* refresh rate. If the hardware composer on the device supports display modes with
* a higher refresh rate than the default value specified here, the framework may use those
* higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
- * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if
- * mDefaultLowRefreshRate is set to 0, but this is not supported anymore.
+ * setFrameRate(). We have historically allowed fallback to mDefaultPeakRefreshRate if
+ * mDefaultRefreshRate is set to 0, but this is not supported anymore.
*/
- private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+ private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the high zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultHighBlockingZoneRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultLowBlockingZoneRefreshRate = DEFAULT_LOW_REFRESH_RATE;
/**
* The display uses different gamma curves for different refresh rates. It's hard for panel
@@ -627,13 +645,7 @@
private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
- // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
- // data, which comes from the ddc, and the current one, which may be the DeviceConfig
- // overwritten value.
- private BrightnessThrottlingData mBrightnessThrottlingData;
- private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
- // The concurrent displays mode might need a stricter throttling policy
- private BrightnessThrottlingData mConcurrentDisplaysBrightnessThrottlingData;
+ private Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap = new HashMap();
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -779,10 +791,6 @@
return config;
}
- void setBrightnessThrottlingData(BrightnessThrottlingData brightnessThrottlingData) {
- mBrightnessThrottlingData = brightnessThrottlingData;
- }
-
/**
* Return the brightness mapping nits array.
*
@@ -1282,18 +1290,11 @@
}
/**
+ * @param id The ID of the throttling data
* @return brightness throttling configuration data for the display.
*/
- public BrightnessThrottlingData getBrightnessThrottlingData() {
- return BrightnessThrottlingData.create(mBrightnessThrottlingData);
- }
-
- /**
- * @return brightness throttling configuration data for the display for the concurrent
- * displays mode.
- */
- public BrightnessThrottlingData getConcurrentDisplaysBrightnessThrottlingData() {
- return BrightnessThrottlingData.create(mConcurrentDisplaysBrightnessThrottlingData);
+ public BrightnessThrottlingData getBrightnessThrottlingData(String id) {
+ return BrightnessThrottlingData.create(mBrightnessThrottlingDataMap.get(id));
}
/**
@@ -1327,15 +1328,29 @@
/**
* @return Default peak refresh rate of the associated display
*/
- public int getDefaultHighRefreshRate() {
- return mDefaultHighRefreshRate;
+ public int getDefaultPeakRefreshRate() {
+ return mDefaultPeakRefreshRate;
}
/**
* @return Default refresh rate of the associated display
*/
- public int getDefaultLowRefreshRate() {
- return mDefaultLowRefreshRate;
+ public int getDefaultRefreshRate() {
+ return mDefaultRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the higher blocking zone of the associated display
+ */
+ public int getDefaultHighBlockingZoneRefreshRate() {
+ return mDefaultHighBlockingZoneRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the lower blocking zone of the associated display
+ */
+ public int getDefaultLowBlockingZoneRefreshRate() {
+ return mDefaultLowBlockingZoneRefreshRate;
}
/**
@@ -1411,8 +1426,7 @@
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
- + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
- + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ + ", mBrightnessThrottlingData=" + mBrightnessThrottlingDataMap
+ "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
@@ -1482,8 +1496,10 @@
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
- + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate
- + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate
+ + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
+ + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
+ + ", mDefaultRefreshRate= " + mDefaultRefreshRate
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1545,8 +1561,7 @@
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMap(config);
- loadBrightnessThrottlingMap(config);
- loadConcurrentDisplaysBrightnessThrottlingMap(config);
+ loadBrightnessThrottlingMaps(config);
loadHighBrightnessModeData(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -1756,76 +1771,47 @@
return Spline.createSpline(nits, ratios);
}
- private void loadBrightnessThrottlingMap(DisplayConfiguration config) {
+ private void loadBrightnessThrottlingMaps(DisplayConfiguration config) {
final ThermalThrottling throttlingConfig = config.getThermalThrottling();
if (throttlingConfig == null) {
Slog.i(TAG, "No thermal throttling config found");
return;
}
- final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap();
- if (map == null) {
+ final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap();
+ if (maps == null || maps.isEmpty()) {
Slog.i(TAG, "No brightness throttling map found");
return;
}
- final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
- // At least 1 point is guaranteed by the display device config schema
- List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
+ for (BrightnessThrottlingMap map : maps) {
+ final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
+ // At least 1 point is guaranteed by the display device config schema
+ List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
+ new ArrayList<>(points.size());
- boolean badConfig = false;
- for (BrightnessThrottlingPoint point : points) {
- ThermalStatus status = point.getThermalStatus();
- if (!thermalStatusIsValid(status)) {
- badConfig = true;
- break;
+ boolean badConfig = false;
+ for (BrightnessThrottlingPoint point : points) {
+ ThermalStatus status = point.getThermalStatus();
+ if (!thermalStatusIsValid(status)) {
+ badConfig = true;
+ break;
+ }
+
+ throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+ convertThermalStatus(status), point.getBrightness().floatValue()));
}
- throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
- }
-
- if (!badConfig) {
- mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
- mOriginalBrightnessThrottlingData = mBrightnessThrottlingData;
- }
- }
-
- private void loadConcurrentDisplaysBrightnessThrottlingMap(DisplayConfiguration config) {
- final ThermalThrottling throttlingConfig = config.getThermalThrottling();
- if (throttlingConfig == null) {
- Slog.i(TAG, "No concurrent displays thermal throttling config found");
- return;
- }
-
- final BrightnessThrottlingMap map =
- throttlingConfig.getConcurrentDisplaysBrightnessThrottlingMap();
- if (map == null) {
- Slog.i(TAG, "No concurrent displays brightness throttling map found");
- return;
- }
-
- final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
- // At least 1 point is guaranteed by the display device config schema
- List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
-
- boolean badConfig = false;
- for (BrightnessThrottlingPoint point : points) {
- ThermalStatus status = point.getThermalStatus();
- if (!thermalStatusIsValid(status)) {
- badConfig = true;
- break;
+ if (!badConfig) {
+ String id = map.getId() == null ? DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
+ : map.getId();
+ if (mBrightnessThrottlingDataMap.containsKey(id)) {
+ throw new RuntimeException("Brightness throttling data with ID " + id
+ + " already exists");
+ }
+ mBrightnessThrottlingDataMap.put(id,
+ BrightnessThrottlingData.create(throttlingLevels));
}
-
- throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
- }
-
- if (!badConfig) {
- mConcurrentDisplaysBrightnessThrottlingData =
- BrightnessThrottlingData.create(throttlingLevels);
}
}
@@ -1838,10 +1824,31 @@
BlockingZoneConfig higherBlockingZoneConfig =
(refreshRateConfigs == null) ? null
: refreshRateConfigs.getHigherBlockingZoneConfigs();
+ loadPeakDefaultRefreshRate(refreshRateConfigs);
+ loadDefaultRefreshRate(refreshRateConfigs);
loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
}
+ private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultPeakRefreshRate() == null) {
+ mDefaultPeakRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultPeakRefreshRate);
+ } else {
+ mDefaultPeakRefreshRate =
+ refreshRateConfigs.getDefaultPeakRefreshRate().intValue();
+ }
+ }
+
+ private void loadDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultRefreshRate() == null) {
+ mDefaultRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRate);
+ } else {
+ mDefaultRefreshRate =
+ refreshRateConfigs.getDefaultRefreshRate().intValue();
+ }
+ }
/**
* Loads the refresh rate configurations pertaining to the upper blocking zones.
@@ -1866,10 +1873,10 @@
private void loadHigherBlockingZoneDefaultRefreshRate(
BlockingZoneConfig upperBlockingZoneConfig) {
if (upperBlockingZoneConfig == null) {
- mDefaultHighRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ mDefaultHighBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fixedRefreshRateInHighZone);
} else {
- mDefaultHighRefreshRate =
+ mDefaultHighBlockingZoneRefreshRate =
upperBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
@@ -1881,10 +1888,10 @@
private void loadLowerBlockingZoneDefaultRefreshRate(
BlockingZoneConfig lowerBlockingZoneConfig) {
if (lowerBlockingZoneConfig == null) {
- mDefaultLowRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultRefreshRate);
+ mDefaultLowBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultRefreshRateInZone);
} else {
- mDefaultLowRefreshRate =
+ mDefaultLowBlockingZoneRefreshRate =
lowerBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 72c9a4a..5f6660b 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1373,7 +1373,7 @@
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultLowRefreshRate();
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1460,7 +1460,7 @@
defaultPeakRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate)
- : (float) displayDeviceConfig.getDefaultHighRefreshRate();
+ : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
}
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
}
@@ -1819,8 +1819,26 @@
return mHighAmbientBrightnessThresholds;
}
+ /**
+ * @return the refresh rate to lock to when in a high brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInHighZone() {
+ return mRefreshRateInHighZone;
+ }
+
+ /**
+ * @return the refresh rate to lock to when in a low brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInLowZone() {
+ return mRefreshRateInLowZone;
+ }
+
private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
+ loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
@@ -1841,6 +1859,44 @@
}
}
+ private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInLowZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInZone)
+ : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInLowZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
+ refreshRateInLowZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInHighZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
+ .getDefaultHighBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInHighZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
+ refreshRateInHighZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
+
private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
@@ -1894,14 +1950,6 @@
}
/**
- * @return the refresh to lock to when in a low brightness zone
- */
- @VisibleForTesting
- int getRefreshRateInLowZone() {
- return mRefreshRateInLowZone;
- }
-
- /**
* @return the display brightness thresholds for the low brightness zones
*/
@VisibleForTesting
@@ -1946,8 +1994,17 @@
mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
}
- mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone();
- mRefreshRateInHighZone = mDeviceConfigDisplaySettings.getRefreshRateInHighZone();
+ final int refreshRateInLowZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInLowZone();
+ if (refreshRateInLowZone != -1) {
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ final int refreshRateInHighZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInHighZone();
+ if (refreshRateInHighZone != -1) {
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
restartObserver();
mDeviceConfigDisplaySettings.startListening();
@@ -2001,6 +2058,10 @@
restartObserver();
}
+ /**
+ * Used to reload the lower blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInLowZone) {
mRefreshRateInLowZone = refreshRate;
@@ -2024,6 +2085,10 @@
restartObserver();
}
+ /**
+ * Used to reload the higher blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInHighZone) {
mRefreshRateInHighZone = refreshRate;
@@ -2874,15 +2939,10 @@
}
public int getRefreshRateInLowZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
- defaultRefreshRateInZone);
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
- return refreshRate;
}
/*
@@ -2904,15 +2964,10 @@
}
public int getRefreshRateInHighZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_fixedRefreshRateInHighZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
- defaultRefreshRateInZone);
-
- return refreshRate;
+ -1);
}
public int getRefreshRateInHbmSunlight() {
@@ -2960,23 +3015,29 @@
int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
- int refreshRateInLowZone = getRefreshRateInLowZone();
+ final int refreshRateInLowZone = getRefreshRateInLowZone();
mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0)
- .sendToTarget();
+
+ if (refreshRateInLowZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
+ 0).sendToTarget();
+ }
int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
- int refreshRateInHighZone = getRefreshRateInHighZone();
+ final int refreshRateInHighZone = getRefreshRateInHighZone();
mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
- .sendToTarget();
+
+ if (refreshRateInHighZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
+ 0).sendToTarget();
+ }
final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index abb0ff6..40eec33 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -504,6 +504,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private String mBrightnessThrottlingDataId;
+
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
@@ -539,6 +541,7 @@
mHandler = new DisplayControllerHandler(handler.getLooper());
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+ mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mBatteryStats = BatteryStatsService.getService();
@@ -862,6 +865,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
+ final String brightnessThrottlingDataId =
+ mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
mHandler.post(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -870,12 +875,19 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
/// Since the underlying display-device changed, we really don't know the
// last command that was sent to change it's state. Lets assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
+ } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ changed = true;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mBrightnessThrottler.resetThrottlingData(
+ config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
changed = true;
@@ -887,9 +899,6 @@
updatePowerState();
}
});
-
- // TODO (b/265793751): Re-create BrightnessTracker if we're enetering/exiting concurrent
- // displays mode
}
/**
@@ -926,7 +935,7 @@
}
private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -948,10 +957,9 @@
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
}
});
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+ mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -2061,11 +2069,8 @@
private BrightnessThrottler createBrightnessThrottlerLocked() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
- final DisplayDeviceConfig.BrightnessThrottlingData data =
- ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
- return new BrightnessThrottler(mHandler, data,
+ return new BrightnessThrottler(mHandler,
+ ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
@@ -2200,15 +2205,13 @@
}
private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
return;
}
final DisplayDeviceConfig.SensorData proxSensor =
mDisplayDeviceConfig.getProximitySensor();
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
- fallbackType);
+ Sensor.TYPE_PROXIMITY);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 2e91bdb..6092ad7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -411,6 +411,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private String mBrightnessThrottlingDataId;
+
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
@@ -443,6 +445,7 @@
mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
+ mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -705,6 +708,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
+ final String brightnessThrottlingDataId =
+ mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
mHandler.post(() -> {
boolean changed = false;
@@ -714,6 +719,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -721,6 +727,12 @@
// last command that was sent to change it's state. Lets assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
+ } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ changed = true;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mBrightnessThrottler.resetThrottlingData(
+ config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
changed = true;
@@ -732,9 +744,6 @@
updatePowerState();
}
});
-
- // TODO (b/265793751): Re-create BrightnessTracker if we're enetering/exiting concurrent
- // displays mode
}
/**
@@ -765,7 +774,7 @@
}
private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -786,10 +795,9 @@
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
}
});
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+ mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -1768,11 +1776,8 @@
private BrightnessThrottler createBrightnessThrottlerLocked() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
- final DisplayDeviceConfig.BrightnessThrottlingData data =
- ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
- return new BrightnessThrottler(mHandler, data,
+ return new BrightnessThrottler(mHandler,
+ ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index a3433d9..5bb3e6b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -346,15 +346,13 @@
}
private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
return;
}
final DisplayDeviceConfig.SensorData proxSensor =
mDisplayDeviceConfig.getProximitySensor();
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
- fallbackType);
+ Sensor.TYPE_PROXIMITY);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dfdbce5..4bb1f0e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -31,6 +31,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.display.layout.Layout;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -152,6 +153,17 @@
// the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
private boolean mIsInTransition;
+ // Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified,
+ // or this is a virtual display etc.
+ private int mPosition = Layout.Display.POSITION_UNKNOWN;
+
+ /**
+ * The ID of the brightness throttling data that should be used. This can change e.g. in
+ * concurrent displays mode in which a stricter brightness throttling policy might need to be
+ * used.
+ */
+ private String mBrightnessThrottlingDataId;
+
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
mLayerStack = layerStack;
@@ -160,6 +172,14 @@
mTempFrameRateOverride = new SparseArray<>();
mIsEnabled = true;
mIsInTransition = false;
+ mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID;
+ }
+
+ public void setPositionLocked(int position) {
+ mPosition = position;
+ }
+ public int getPositionLocked() {
+ return mPosition;
}
/**
@@ -416,6 +436,11 @@
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
+
+ if (mPosition == Layout.Display.POSITION_REAR) {
+ mBaseDisplayInfo.flags |= Display.FLAG_REAR;
+ }
+
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
@@ -762,7 +787,7 @@
/**
* Sets the display as enabled.
*
- * @param enable True if enabled, false otherwise.
+ * @param enabled True if enabled, false otherwise.
*/
public void setEnabledLocked(boolean enabled) {
mIsEnabled = enabled;
@@ -785,11 +810,28 @@
mIsInTransition = isInTransition;
}
+ /**
+ * @return The ID of the brightness throttling data that this display should use.
+ */
+ public String getBrightnessThrottlingDataIdLocked() {
+ return mBrightnessThrottlingDataId;
+ }
+
+ /**
+ * @param brightnessThrottlingDataId The ID of the brightness throttling data that this
+ * display should use.
+ */
+ public void setBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
+ mBrightnessThrottlingDataId =
+ brightnessThrottlingDataId;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
pw.println("mIsInTransition=" + mIsInTransition);
pw.println("mLayerStack=" + mLayerStack);
+ pw.println("mPosition=" + mPosition);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -802,6 +844,7 @@
pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
+ pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 5087caa..a67644b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -638,7 +638,8 @@
& DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
applyLayoutLocked();
return;
}
@@ -976,6 +977,7 @@
// Now that we have a display-device, we need a LogicalDisplay to map it to. Find the
// right one, if it doesn't exist, create a new one.
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+
LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
if (newDisplay == null) {
newDisplay = createNewLogicalDisplayLocked(
@@ -988,7 +990,12 @@
newDisplay.swapDisplaysLocked(oldDisplay);
}
+ newDisplay.setPositionLocked(displayLayout.getPosition());
setEnabledLocked(newDisplay, displayLayout.isEnabled());
+ newDisplay.setBrightnessThrottlingDataIdLocked(
+ displayLayout.getBrightnessThrottlingMapId() == null
+ ? DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
+ : displayLayout.getBrightnessThrottlingMapId());
}
}
@@ -1063,7 +1070,7 @@
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
- mIdProducer);
+ mIdProducer, /* brightnessThrottlingMapId= */ null);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 4a466fd..01aa97a 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -18,6 +18,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
@@ -25,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Holds a collection of {@link Display}s. A single instance of this class describes
@@ -76,7 +79,23 @@
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId) {
+ return createDisplayLocked(address, isDefault, isEnabled, idProducer,
+ brightnessThrottlingMapId, POSITION_UNKNOWN);
+ }
+
+ /**
+ * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+ *
+ * @param address Address of the device.
+ * @param isDefault Indicates if the device is meant to be the default display.
+ * @param isEnabled Indicates if this display is usable and can be switched on
+ * @param position Indicates the position this display is facing in this layout.
+ * @return The new layout.
+ */
+ public Display createDisplayLocked(
+ @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -93,7 +112,8 @@
// different layouts, a logical display can be destroyed and later recreated with the
// same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
- final Display display = new Display(address, logicalDisplayId, isEnabled);
+ final Display display = new Display(address, logicalDisplayId, isEnabled,
+ brightnessThrottlingMapId, position);
mDisplays.add(display);
return display;
@@ -195,11 +215,19 @@
// {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
private int mPosition;
- Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
+ // The ID of the brightness throttling map that should be used. This can change e.g. in
+ // concurrent displays mode in which a stricter brightness throttling policy might need to
+ // be used.
+ @Nullable
+ private final String mBrightnessThrottlingMapId;
+
+ Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
+ String brightnessThrottlingMapId, int position) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
- mPosition = POSITION_UNKNOWN;
+ mPosition = position;
+ mBrightnessThrottlingMapId = brightnessThrottlingMapId;
}
@Override
@@ -209,6 +237,7 @@
+ "(" + (mIsEnabled ? "ON" : "OFF") + ")"
+ ", addr: " + mAddress
+ ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ + ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ "}";
}
@@ -223,7 +252,9 @@
return otherDisplay.mIsEnabled == this.mIsEnabled
&& otherDisplay.mPosition == this.mPosition
&& otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
- && this.mAddress.equals(otherDisplay.mAddress);
+ && this.mAddress.equals(otherDisplay.mAddress)
+ && Objects.equals(mBrightnessThrottlingMapId,
+ otherDisplay.mBrightnessThrottlingMapId);
}
@Override
@@ -233,6 +264,7 @@
result = 31 * result + mPosition;
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mAddress.hashCode();
+ result = 31 * result + mBrightnessThrottlingMapId.hashCode();
return result;
}
@@ -251,5 +283,16 @@
public void setPosition(int position) {
mPosition = position;
}
+
+ /**
+ * @return The ID of the brightness throttling map that this display should use.
+ */
+ public String getBrightnessThrottlingMapId() {
+ return mBrightnessThrottlingMapId;
+ }
+
+ public int getPosition() {
+ return mPosition;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 85d5b4f..5b772fc 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -419,16 +419,16 @@
float ambientBrightness = mBrightnessFilter.getEstimate(time);
mLatestAmbientBrightness = ambientBrightness;
- if (ambientColorTemperature != -1.0f &&
- mLowLightAmbientBrightnessToBiasSpline != null) {
+ if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+ && mLowLightAmbientBrightnessToBiasSpline != null) {
float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
bias * ambientColorTemperature + (1.0f - bias)
* mLowLightAmbientColorTemperature;
mLatestLowLightBias = bias;
}
- if (ambientColorTemperature != -1.0f &&
- mHighLightAmbientBrightnessToBiasSpline != null) {
+ if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+ && mHighLightAmbientBrightnessToBiasSpline != null) {
float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
(1.0f - bias) * ambientColorTemperature + bias
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 1a357ee..41053e9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -21,11 +21,14 @@
import android.annotation.Nullable;
import android.app.IGrammaticalInflectionManager;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
+import android.util.Log;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -36,9 +39,10 @@
* <p>This service is API entry point for storing app-specific grammatical inflection.
*/
public class GrammaticalInflectionService extends SystemService {
-
+ private final String TAG = "GrammaticalInflection";
private final GrammaticalInflectionBackupHelper mBackupHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private PackageManagerInternal mPackageManagerInternal;
private static final String GRAMMATICAL_INFLECTION_ENABLED =
"i18n.grammatical_Inflection.enabled";
@@ -55,6 +59,7 @@
public GrammaticalInflectionService(Context context) {
super(context);
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBackupHelper = new GrammaticalInflectionBackupHelper(
this, context.getPackageManager());
}
@@ -110,14 +115,26 @@
protected void setRequestedApplicationGrammaticalGender(
String appPackageName, int userId, int gender) {
- if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
- return;
- }
-
+ int preValue = getApplicationGrammaticalGender(appPackageName, userId);
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
+ if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
+ if (preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ Log.d(TAG, "Clearing the user's grammatical gender setting");
+ updater.setGrammaticalGender(GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
+ }
+ return;
+ }
+
+ final int uid = mPackageManagerInternal.getPackageUid(appPackageName, 0, userId);
+ FrameworkStatsLog.write(FrameworkStatsLog.GRAMMATICAL_INFLECTION_CHANGED,
+ FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
+ uid,
+ gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+
updater.setGrammaticalGender(gender).commit();
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index cd972dc..ea000a0 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -513,7 +513,7 @@
WeaverConfig weaverConfig = null;
try {
weaverConfig = weaver.getConfig();
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Slog.e(TAG, "Failed to get weaver config", e);
}
if (weaverConfig == null || weaverConfig.slots <= 0) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index f2a39b8..26e8fe6 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -21,11 +21,11 @@
import android.media.Session2Token;
import android.media.session.MediaSession;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
-import com.android.server.utils.EventLogger;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -61,8 +61,6 @@
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
- private final EventLogger mEventLogger = new EventLogger(DUMP_EVENTS_MAX_COUNT, TAG);
-
/**
* The media button session which receives media key events.
* It could be null if the previous media button session is released.
@@ -86,9 +84,8 @@
* @param record The record to add.
*/
public void addSession(MediaSessionRecordImpl record) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "addSession() (to bottom of stack)",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "addSession to bottom of stack | record: %s",
record
));
mSessions.add(record);
@@ -106,9 +103,8 @@
* @param record The record to remove.
*/
public void removeSession(MediaSessionRecordImpl record) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "removeSession()",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "removeSession | record: %s",
record
));
mSessions.remove(record);
@@ -156,9 +152,8 @@
public void onPlaybackStateChanged(
MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
if (shouldUpdatePriority) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "onPlaybackStateChanged() - Pushing session to top",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "onPlaybackStateChanged - Pushing session to top | record: %s",
record
));
mSessions.remove(record);
@@ -365,8 +360,6 @@
for (MediaSessionRecordImpl record : mSessions) {
record.dump(pw, indent);
}
- pw.println(prefix + "Session stack events:");
- mEventLogger.dump(pw, indent);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5ab4a42..14ae2a7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -521,8 +521,6 @@
private PackageManager mPackageManagerClient;
PackageManagerInternal mPackageManagerInternal;
private PermissionPolicyInternal mPermissionPolicyInternal;
-
- private PermissionManagerServiceInternal mPermissionInternal;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
// Can be null for wear
@@ -2212,8 +2210,7 @@
TelephonyManager telephonyManager, ActivityManagerInternal ami,
MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
UsageStatsManagerInternal usageStatsManagerInternal,
- TelecomManager telecomManager, NotificationChannelLogger channelLogger,
- PermissionManagerServiceInternal permInternal) {
+ TelecomManager telecomManager, NotificationChannelLogger channelLogger) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2231,7 +2228,6 @@
mPackageManagerClient = packageManagerClient;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
- mPermissionInternal = permInternal;
mUmInternal = LocalServices.getService(UserManagerInternal.class);
mUsageStatsManagerInternal = usageStatsManagerInternal;
mAppOps = appOps;
@@ -2543,8 +2539,7 @@
AppGlobals.getPermissionManager()),
LocalServices.getService(UsageStatsManagerInternal.class),
getContext().getSystemService(TelecomManager.class),
- new NotificationChannelLoggerImpl(),
- LocalServices.getService(PermissionManagerServiceInternal.class));
+ new NotificationChannelLoggerImpl());
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -6694,30 +6689,17 @@
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
- int canColorize = mPermissionInternal.checkPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg, userId);
+ int canColorize = mPackageManagerClient.checkPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
if (canColorize == PERMISSION_GRANTED) {
notification.flags |= Notification.FLAG_CAN_COLORIZE;
} else {
notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
}
- if (notification.extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, false)) {
- int hasShowDuringSetupPerm = mPermissionInternal.checkPermission(
- android.Manifest.permission.NOTIFICATION_DURING_SETUP, pkg, userId);
- if (hasShowDuringSetupPerm != PERMISSION_GRANTED) {
- notification.extras.remove(Notification.EXTRA_ALLOW_DURING_SETUP);
- if (DBG) {
- Slog.w(TAG, "warning: pkg " + pkg + " attempting to show during setup"
- + " without holding perm "
- + Manifest.permission.NOTIFICATION_DURING_SETUP);
- }
- }
- }
-
if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
- int fullscreenIntentPermission = mPermissionInternal.checkPermission(
- android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg, userId);
+ int fullscreenIntentPermission = mPackageManagerClient.checkPermission(
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);
if (fullscreenIntentPermission != PERMISSION_GRANTED) {
notification.fullScreenIntent = null;
Slog.w(TAG, "Package " + pkg +
@@ -6725,6 +6707,31 @@
}
}
+ // Ensure all actions are present
+ if (notification.actions != null) {
+ boolean hasNullActions = false;
+ int nActions = notification.actions.length;
+ for (int i = 0; i < nActions; i++) {
+ if (notification.actions[i] == null) {
+ hasNullActions = true;
+ break;
+ }
+ }
+ if (hasNullActions) {
+ ArrayList<Notification.Action> nonNullActions = new ArrayList<>();
+ for (int i = 0; i < nActions; i++) {
+ if (notification.actions[i] != null) {
+ nonNullActions.add(notification.actions[i]);
+ }
+ }
+ if (nonNullActions.size() != 0) {
+ notification.actions = nonNullActions.toArray(new Notification.Action[0]);
+ } else {
+ notification.actions = null;
+ }
+ }
+ }
+
// Ensure CallStyle has all the correct actions
if (notification.isStyle(Notification.CallStyle.class)) {
Notification.Builder builder =
@@ -6737,7 +6744,7 @@
// Ensure MediaStyle has correct permissions for remote device extras
if (notification.isStyle(Notification.MediaStyle.class)) {
- int hasMediaContentControlPermission = mPermissionInternal.checkPermission(
+ int hasMediaContentControlPermission = mPackageManager.checkPermission(
android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
@@ -6752,7 +6759,7 @@
// Ensure only allowed packages have a substitute app name
if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
- int hasSubstituteAppNamePermission = mPermissionInternal.checkPermission(
+ int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
@@ -8356,9 +8363,15 @@
}
private boolean isExemptFromRateLimiting(String pkg, int userId) {
- return mPermissionInternal.checkPermission(
- android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
- == PackageManager.PERMISSION_GRANTED;
+ boolean isExemptFromRateLimiting = false;
+ try {
+ isExemptFromRateLimiting = mPackageManager.checkPermission(
+ android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect with package manager");
+ }
+ return isExemptFromRateLimiting;
}
/** Reports rate limiting toasts compat change (used when the toast was blocked). */
@@ -10005,9 +10018,13 @@
boolean canUseManagedServices(String pkg, Integer userId, String requiredPermission) {
boolean canUseManagedServices = true;
if (requiredPermission != null) {
- if (mPermissionInternal.checkPermission(requiredPermission, pkg, userId)
- != PackageManager.PERMISSION_GRANTED) {
- canUseManagedServices = false;
+ try {
+ if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
+ != PackageManager.PERMISSION_GRANTED) {
+ canUseManagedServices = false;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "can't talk to pm", e);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd..e698c4b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.os;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -25,6 +26,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
import android.os.IDumpstate;
import android.os.IDumpstateListener;
@@ -35,10 +37,13 @@
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemConfig;
import java.io.FileDescriptor;
@@ -60,18 +65,105 @@
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportWhitelistedPackages;
+ private final BugreportFileManager mBugreportFileManager;
@GuardedBy("mLock")
private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
- BugreportManagerServiceImpl(Context context) {
- mContext = context;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- mBugreportWhitelistedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ /** Helper class for associating previously generated bugreports with their callers. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static class BugreportFileManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
+
+ BugreportFileManager() {
+ mBugreportFiles = new ArrayMap<>();
+ }
+
+ /**
+ * Checks that a given file was generated on behalf of the given caller. If the file was
+ * generated on behalf of the caller, it is removed from the bugreport mapping so that it
+ * may not be retrieved again. If the file was not generated on behalf of the caller, an
+ * {@link IllegalArgumentException} is thrown.
+ *
+ * @param callingInfo a (uid, package name) pair identifying the caller
+ * @param bugreportFile the file name which was previously given to the caller in the
+ * {@link BugreportCallback#onFinished(String)} callback.
+ *
+ * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
+ * {@code callingInfo}.
+ */
+ void ensureCallerPreviouslyGeneratedFile(
+ Pair<Integer, String> callingInfo, String bugreportFile) {
+ synchronized (mLock) {
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+ if (bugreportFilesForCaller != null
+ && bugreportFilesForCaller.contains(bugreportFile)) {
+ bugreportFilesForCaller.remove(bugreportFile);
+ if (bugreportFilesForCaller.isEmpty()) {
+ mBugreportFiles.remove(callingInfo);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "File " + bugreportFile + " was not generated"
+ + " on behalf of calling package " + callingInfo.second);
+ }
+ }
+ }
+
+ /**
+ * Associates a bugreport file with a caller, which is identified as a
+ * (uid, package name) pair.
+ */
+ void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+ synchronized (mLock) {
+ if (!mBugreportFiles.containsKey(caller)) {
+ mBugreportFiles.put(caller, new ArraySet<>());
+ }
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
+ bugreportFilesForCaller.add(bugreportFile);
+ }
+ }
}
+ static class Injector {
+ Context mContext;
+ ArraySet<String> mAllowlistedPackages;
+
+ Injector(Context context, ArraySet<String> allowlistedPackages) {
+ mContext = context;
+ mAllowlistedPackages = allowlistedPackages;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ ArraySet<String> getAllowlistedPackages() {
+ return mAllowlistedPackages;
+ }
+
+ }
+
+ BugreportManagerServiceImpl(Context context) {
+ this(new Injector(context, SystemConfig.getInstance().getBugreportWhitelistedPackages()));
+
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ BugreportManagerServiceImpl(Injector injector) {
+ mContext = injector.getContext();
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mBugreportFileManager = new BugreportFileManager();
+ mBugreportWhitelistedPackages =
+ injector.getAllowlistedPackages();
+ }
+
+
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void preDumpUiData(String callingPackage) {
@@ -135,6 +227,50 @@
}
}
+ @Override
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void retrieveBugreport(int callingUidUnused, String callingPackage,
+ FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+ int callingUid = Binder.getCallingUid();
+ enforcePermission(callingPackage, callingUid, false);
+
+ try {
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ new Pair<>(callingUid, callingPackage), bugreportFile);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, e.getMessage());
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (isDumpstateBinderServiceRunningLocked()) {
+ Slog.w(TAG, "'dumpstate' is already running. Cannot retrieve a bugreport"
+ + " while another one is currently in progress.");
+ reportError(listener,
+ IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ return;
+ }
+
+ IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+ if (ds == null) {
+ Slog.w(TAG, "Unable to get bugreport service");
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+ return;
+ }
+
+ // Wrap the listener so we can intercept binder events directly.
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage));
+ try {
+ ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
+ bugreportFile, myListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in retrieveBugreport", e);
+ }
+ }
+ }
+
private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -148,7 +284,9 @@
}
private void validateBugreportFlags(int flags) {
- flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ flags = clearBugreportFlag(flags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
if (flags != 0) {
Slog.w(TAG, "Unknown bugreport flags: " + flags);
throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -298,6 +436,9 @@
}
}
+ boolean isConsentDeferred =
+ (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -306,7 +447,8 @@
}
// Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds);
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
bugreportFlags, myListener, isScreenshotRequested);
@@ -405,10 +547,13 @@
private final IDumpstateListener mListener;
private final IDumpstate mDs;
private boolean mDone = false;
+ private final Pair<Integer, String> mCaller;
- DumpstateListener(IDumpstateListener listener, IDumpstate ds) {
+ DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+ @Nullable Pair<Integer, String> caller) {
mListener = listener;
mDs = ds;
+ mCaller = caller;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -430,11 +575,14 @@
}
@Override
- public void onFinished() throws RemoteException {
+ public void onFinished(String bugreportFile) throws RemoteException {
synchronized (mLock) {
mDone = true;
}
- mListener.onFinished();
+ if (mCaller != null) {
+ mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ }
+ mListener.onFinished(bugreportFile);
}
@Override
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 094b182..58a609e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1856,8 +1856,7 @@
// Figure out which lib versions the caller can see
LongSparseLongArray versionsCallerCanSee = null;
final int callingAppId = UserHandle.getAppId(callingUid);
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
- && callingAppId != Process.ROOT_UID) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingAppId)) {
versionsCallerCanSee = new LongSparseLongArray();
String libName = versionedLib.valueAt(0).getName();
String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
@@ -2034,8 +2033,7 @@
if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
// System/shell/root get to see all static libs
final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
- || appId == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(appId)) {
return false;
}
// Installer gets to see all static libs.
@@ -2091,8 +2089,7 @@
if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
// System/shell/root get to see all SDK libs.
final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
- || appId == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(appId)) {
return false;
}
// Installer gets to see all SDK libs.
@@ -2152,7 +2149,7 @@
if (!requirePermissionWhenSameUser && userId == callingUserId) {
return true;
}
- if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
return true;
}
if (requireFullPermission) {
@@ -3813,8 +3810,7 @@
public boolean canRequestPackageInstalls(@NonNull String packageName, int callingUid,
int userId, boolean throwIfPermNotDeclared) {
int uid = getPackageUidInternal(packageName, 0, userId, callingUid);
- if (callingUid != uid && callingUid != Process.ROOT_UID
- && callingUid != Process.SYSTEM_UID) {
+ if (callingUid != uid && !PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
throw new SecurityException(
"Caller uid " + callingUid + " does not own package " + packageName);
}
@@ -5540,8 +5536,8 @@
enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/,
true /*checkShell*/, "getHarmfulAppInfo");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
- checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
+ && checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index e0de294..225d2a4 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -840,7 +840,7 @@
private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
String pkgName, int userId) {
- if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID
+ if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return true;
}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index ae80bab..babcd33 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -145,7 +145,7 @@
mHandler.post(() -> {
var pendingCheck = new PendingInstallConstraintsCheck(
packageNames, constraints, resultFuture, timeoutMillis);
- var deviceIdleFuture = constraints.isRequireDeviceIdle()
+ var deviceIdleFuture = constraints.isDeviceIdleRequired()
? checkDeviceIdle() : CompletableFuture.completedFuture(false);
deviceIdleFuture.thenAccept(isIdle -> {
Preconditions.checkState(mHandler.getLooper().isCurrentThread());
@@ -217,14 +217,14 @@
@WorkerThread
private boolean areConstraintsSatisfied(List<String> packageNames,
InstallConstraints constraints, boolean isIdle) {
- return (!constraints.isRequireDeviceIdle() || isIdle)
- && (!constraints.isRequireAppNotForeground()
+ return (!constraints.isDeviceIdleRequired() || isIdle)
+ && (!constraints.isAppNotForegroundRequired()
|| !mAppStateHelper.hasForegroundApp(packageNames))
- && (!constraints.isRequireAppNotInteracting()
+ && (!constraints.isAppNotInteractingRequired()
|| !mAppStateHelper.hasInteractingApp(packageNames))
- && (!constraints.isRequireAppNotTopVisible()
+ && (!constraints.isAppNotTopVisibleRequired()
|| !mAppStateHelper.hasTopVisibleApp(packageNames))
- && (!constraints.isRequireNotInCall()
+ && (!constraints.isNotInCallRequired()
|| !mAppStateHelper.isInCall());
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d23ea16..6546f6a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -663,7 +663,7 @@
&& params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
? params.installerPackageName : installerPackageName;
- if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)
+ if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| PackageInstallerSession.isSystemDataLoaderInstallation(params)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not the
@@ -706,7 +706,7 @@
}
}
- if (Build.IS_DEBUGGABLE || isCalledBySystem(callingUid)) {
+ if (Build.IS_DEBUGGABLE || PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
@@ -748,7 +748,8 @@
if (params.isMultiPackage) {
throw new IllegalArgumentException("A multi-session can't be set as APEX.");
}
- if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
+ || mBypassNextAllowedApexUpdateCheck) {
params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
} else {
// Only specific APEX updates (installed through ADB, or for CTS tests) can disable
@@ -758,20 +759,20 @@
}
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
- && !isCalledBySystemOrShell(callingUid)
+ && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
&& (snapshot.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM)
== 0) {
throw new SecurityException(
"Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag.");
}
- if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
+ if (params.isStaged && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException("Installer not allowed to commit staged install");
}
}
- if (isApex && !isCalledBySystemOrShell(callingUid)) {
+ if (isApex && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException(
@@ -874,7 +875,7 @@
// reset the force queryable param if it's not called by an approved caller.
if (params.forceQueryableOverride) {
- if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
params.forceQueryableOverride = false;
}
}
@@ -916,15 +917,6 @@
return sessionId;
}
- private static boolean isCalledBySystem(int callingUid) {
- return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID;
- }
-
- private boolean isCalledBySystemOrShell(int callingUid) {
- return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
- || callingUid == Process.SHELL_UID;
- }
-
private boolean isStagedInstallerAllowed(String installerName) {
return SystemConfig.getInstance().getWhitelistedStagedInstallers().contains(installerName);
}
@@ -1189,7 +1181,7 @@
final Computer snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
- if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}
@@ -1243,7 +1235,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES, null);
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
- if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}
@@ -1280,7 +1272,7 @@
final var snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
- if (!isCalledBySystemOrShell(callingUid)) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
for (var packageName : packageNames) {
var ps = snapshot.getPackageStateInternal(packageName);
if (ps == null || !TextUtils.equals(
@@ -1367,7 +1359,7 @@
@Override
public void bypassNextStagedInstallerCheck(boolean value) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass staged installer check");
}
mBypassNextStagedInstallerCheck = value;
@@ -1375,7 +1367,7 @@
@Override
public void bypassNextAllowedApexUpdateCheck(boolean value) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass allowed apex update check");
}
mBypassNextAllowedApexUpdateCheck = value;
@@ -1383,7 +1375,7 @@
@Override
public void disableVerificationForUid(int uid) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Operation not allowed for caller");
}
mDisableVerificationForUid = uid;
@@ -1394,7 +1386,7 @@
*/
@Override
public void setAllowUnlimitedSilentUpdates(@Nullable String installerPackageName) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to unlimite silent updates");
}
mSilentUpdatePolicy.setAllowUnlimitedSilentUpdates(installerPackageName);
@@ -1405,7 +1397,7 @@
*/
@Override
public void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to set silent updates throttle time");
}
mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c6a1579..5d41b4c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3212,7 +3212,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
callingMethod);
- if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingUid)
&& UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
+ userId);
@@ -5323,7 +5323,7 @@
snapshot.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
true /*checkShell*/, "isPackageStateProtected");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
&& snapshot.checkUidPermission(MANAGE_DEVICE_ADMINS, callingUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
@@ -5858,8 +5858,8 @@
snapshot.enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/,
true /*checkShell*/, "setHarmfulAppInfo");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
- snapshot.checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid)
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
+ && snapshot.checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
+ SET_HARMFUL_APP_WARNINGS + " permission.");
@@ -6613,7 +6613,7 @@
public void uninstallApex(String packageName, long versionCode, int userId,
IntentSender intentSender, int flags) {
final int callerUid = Binder.getCallingUid();
- if (callerUid != Process.ROOT_UID && callerUid != Process.SHELL_UID) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
throw new SecurityException("Not allowed to uninstall apexes");
}
PackageInstallerService.PackageDeleteObserverAdapter adapter =
@@ -6658,7 +6658,7 @@
final int callingUid = Binder.getCallingUid();
final Computer snapshot = snapshotComputer();
final String[] callerPackageNames = snapshot.getPackagesForUid(callingUid);
- if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)
&& !ArrayUtils.contains(callerPackageNames, packageName)) {
throw new SecurityException("dumpProfiles");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index b919330..928ffa7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1372,10 +1372,24 @@
*/
public static boolean isSystemOrRoot() {
final int uid = Binder.getCallingUid();
+ return isSystemOrRoot(uid);
+ }
+
+ /**
+ * Check if a UID is system UID or root's UID.
+ */
+ public static boolean isSystemOrRoot(int uid) {
return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID;
}
/**
+ * Check if a UID is system UID or shell's UID.
+ */
+ public static boolean isRootOrShell(int uid) {
+ return uid == Process.ROOT_UID || uid == Process.SHELL_UID;
+ }
+
+ /**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
*
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4f0a115..9c91879 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,7 +89,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -121,6 +120,7 @@
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
+import com.android.server.security.FileIntegrityLocal;
import com.android.server.utils.Slogf;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
@@ -2714,8 +2714,8 @@
}
try {
- VerityUtils.setUpFsverity(mSettingsFilename.getAbsolutePath());
- VerityUtils.setUpFsverity(mSettingsReserveCopyFilename.getAbsolutePath());
+ FileIntegrityLocal.setUpFsVerity(mSettingsFilename.getAbsolutePath());
+ FileIntegrityLocal.setUpFsVerity(mSettingsReserveCopyFilename.getAbsolutePath());
} catch (IOException e) {
Slog.e(TAG, "Failed to verity-protect settings", e);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362ddd..6d5e2b0 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -885,7 +885,12 @@
* available ShareTarget definitions in this package.
*/
public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
- @NonNull IntentFilter filter) {
+ @NonNull final IntentFilter filter) {
+ return getMatchingShareTargets(filter, null);
+ }
+
+ List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
+ @NonNull final IntentFilter filter, @Nullable final String pkgName) {
synchronized (mLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
@@ -909,8 +914,7 @@
// included in the result
findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
- mShortcutUser.mService.mContext.getPackageName(),
- 0, /*getPinnedByAnyLauncher=*/ false);
+ pkgName, 0, /*getPinnedByAnyLauncher=*/ false);
final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
for (int i = 0; i < shortcuts.size(); i++) {
@@ -1108,7 +1112,7 @@
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
- final int shareTargetSize;
+ int shareTargetSize = 0;
synchronized (mLock) {
try {
shareTargetSize = mShareTargets.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 83720f1..1cd9ec6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2512,11 +2512,17 @@
}
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"getShareTargets");
+ final ComponentName chooser = injectChooserActivity();
+ final String pkg = (chooser != null
+ && mPackageManagerInternal.getComponentEnabledSetting(chooser,
+ injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ ? chooser.getPackageName() : mContext.getPackageName();
synchronized (mLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
- user.forAllPackages(p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter)));
+ user.forAllPackages(p -> shortcutInfoList.addAll(
+ p.getMatchingShareTargets(filter, pkg)));
return new ParceledListSlice<>(shortcutInfoList);
}
}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 35c42a3..fac681a 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -943,11 +943,7 @@
return false;
}
- // System apps are never considered stopped for purposes of
- // filtering, because there may be no way for the user to
- // actually re-launch them.
- return !packageState.isSystem()
- && packageState.getUserStateOrDefault(userId).isStopped();
+ return packageState.getUserStateOrDefault(userId).isStopped();
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index db939d9..5ca3333 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -334,6 +334,8 @@
static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
static public final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
+
private static final String TALKBACK_LABEL = "TalkBack";
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
@@ -4902,10 +4904,15 @@
// ... eventually calls finishWindowsDrawn which will finalize our screen turn on
// as well as enabling the orientation change logic/sensor.
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display");
mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
INVALID_DISPLAY, 0));
+
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
}, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
}
@@ -4961,10 +4968,16 @@
}
} else {
mScreenOnListeners.put(displayId, screenOnListener);
+
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId);
mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
displayId, 0));
+
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
}, WAITING_FOR_DRAWN_TIMEOUT, displayId);
}
}
diff --git a/services/core/java/com/android/server/security/FileIntegrityLocal.java b/services/core/java/com/android/server/security/FileIntegrityLocal.java
new file mode 100644
index 0000000..8c7219b
--- /dev/null
+++ b/services/core/java/com/android/server/security/FileIntegrityLocal.java
@@ -0,0 +1,44 @@
+/*
+ * 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.server.security;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.security.VerityUtils;
+
+import java.io.IOException;
+
+/**
+ * In-process API for server side FileIntegrity related infrastructure.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public final class FileIntegrityLocal {
+ private FileIntegrityLocal() {}
+
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull String filePath) throws IOException {
+ VerityUtils.setUpFsverity(filePath);
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 6d39177..a694f5f 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -863,8 +863,9 @@
*/
private void enforceObserveSensorPrivacyPermission() {
String systemUIPackage = mContext.getString(R.string.config_systemUi);
- if (Binder.getCallingUid() == mPackageManagerInternal
- .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+ int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
+ .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM));
+ if (UserHandle.getCallingAppId() == systemUIAppId) {
// b/221782106, possible race condition with role grant might bootloop device.
return;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 262b964..5fd57e3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.wallpaper;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
@@ -67,6 +66,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -759,6 +759,7 @@
private final Context mContext;
private final WindowManagerInternal mWindowManagerInternal;
+ private final PackageManagerInternal mPackageManagerInternal;
private final IPackageManager mIPackageManager;
private final ActivityManager mActivityManager;
private final MyPackageMonitor mMonitor;
@@ -1604,6 +1605,7 @@
context.getResources().getString(R.string.image_wallpaper_component));
mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
@@ -2283,20 +2285,23 @@
@Override
public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
- final boolean allow =
- hasPermission(READ_WALLPAPER_INTERNAL) || hasPermission(QUERY_ALL_PACKAGES);
- if (allow) {
- userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
- synchronized (mLock) {
- WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
- : mWallpaperMap.get(userId);
- if (wallpaper != null && wallpaper.connection != null) {
- return wallpaper.connection.mInfo;
- }
+
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
+ synchronized (mLock) {
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null
+ || wallpaper.connection == null
+ || wallpaper.connection.mInfo == null) return null;
+
+ WallpaperInfo info = wallpaper.connection.mInfo;
+ if (hasPermission(READ_WALLPAPER_INTERNAL)
+ || mPackageManagerInternal.canQueryPackage(
+ Binder.getCallingUid(), info.getComponent().getPackageName())) {
+ return info;
}
}
-
return null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 316b12a..4507637 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -749,6 +749,8 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
+ EventLogTags.writeWmSetRequestedOrientation(requestedOrientation,
+ r.shortComponentName);
r.setRequestedOrientation(requestedOrientation);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0c97d7d..c289153 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10333,8 +10333,8 @@
// TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
// covered with bubbles.
boolean shouldSendCompatFakeFocus() {
- return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled(info)
- && inMultiWindowMode() && !inPinnedWindowingMode() && !inFreeformWindowingMode();
+ return mLetterboxUiController.shouldSendFakeFocus() && inMultiWindowMode()
+ && !inPinnedWindowingMode() && !inFreeformWindowingMode();
}
static class Builder {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3ffb2fa..e04900c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+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;
@@ -34,6 +36,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -44,10 +47,13 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.widget.Toast;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.UiThread;
import java.util.Map;
import java.util.Set;
@@ -232,6 +238,27 @@
activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
}
+ /**
+ * Notifies that animation in {@link ScreenAnimationRotation} has finished.
+ *
+ * <p>This class uses this signal as a trigger for notifying the user about forced rotation
+ * reason with the {@link Toast}.
+ */
+ void onScreenRotationAnimationFinished() {
+ if (!isTreatmentEnabledForDisplay() || mCameraIdPackageBiMap.isEmpty()) {
+ return;
+ }
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null
+ // Checking windowing mode on activity level because we don't want to
+ // show toast in case of activity embedding.
+ || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
String getSummaryForDisplayRotationHistoryRecord() {
String summaryIfEnabled = "";
if (isTreatmentEnabledForDisplay()) {
@@ -281,6 +308,10 @@
&& mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
}
+ boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ return isTreatmentEnabledForDisplay() && isCameraActiveInFullscreen(activity);
+ }
+
/**
* Whether camera compat treatment is applicable for the given activity.
*
@@ -292,12 +323,16 @@
* </ul>
*/
boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- return activity != null && !activity.inMultiWindowMode()
+ return activity != null && isCameraActiveInFullscreen(activity)
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
- && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
+ && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
+ }
+
+ private boolean isCameraActiveInFullscreen(@NonNull ActivityRecord activity) {
+ return !activity.inMultiWindowMode()
&& mCameraIdPackageBiMap.containsPackageName(activity.packageName)
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -334,7 +369,31 @@
}
mCameraIdPackageBiMap.put(packageName, cameraId);
}
- updateOrientationWithWmLock();
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null || topActivity.getTask() == null) {
+ return;
+ }
+ // Checking whether an activity in fullscreen rather than the task as this camera compat
+ // treatment doesn't cover activity embedding.
+ if (topActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+ topActivity.recomputeConfiguration();
+ }
+ updateOrientationWithWmLock();
+ return;
+ }
+ // Checking that the whole app is in multi-window mode as we shouldn't show toast
+ // for the activity embedding case.
+ if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+ }
+ }
+
+ @VisibleForTesting
+ void showToast(@StringRes int stringRes) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
}
private synchronized void notifyCameraClosed(@NonNull String cameraId) {
@@ -375,6 +434,17 @@
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera %s is closed, updating rotation.",
mDisplayContent.mDisplayId, cameraId);
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null
+ // Checking whether an activity in fullscreen rather than the task as this camera
+ // compat treatment doesn't cover activity embedding.
+ || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+ topActivity.recomputeConfiguration();
+ }
updateOrientationWithWmLock();
}
@@ -396,6 +466,10 @@
private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+ boolean isEmpty() {
+ return mCameraIdToPackageMap.isEmpty();
+ }
+
void put(String packageName, String cameraId) {
// Always using the last connected camera ID for the package even for the concurrent
// camera use case since we can't guess which camera is more important anyway.
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index b36f835..031e022 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.wm
@@ -59,6 +59,10 @@
31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
+
+# Set the requested orientation of an activity.
+31006 wm_set_requested_orientation (Orientation|1|5),(Component Name|3)
+
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index b64420a..7066a33 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,14 +19,13 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.graphics.Color;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -310,6 +309,9 @@
mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ mIsCameraCompatTreatmentEnabled,
+ /* key */ KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
+ mDeviceConfig.updateFlagActiveStatus(
/* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
/* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
mDeviceConfig.updateFlagActiveStatus(
@@ -1062,38 +1064,8 @@
"enable_translucent_activity_letterbox", false);
}
- @VisibleForTesting
- boolean getPackageManagerProperty(PackageManager pm, String property) {
- boolean enabled = false;
- try {
- final PackageManager.Property p = pm.getProperty(property, mContext.getPackageName());
- enabled = p.getBoolean();
- } catch (PackageManager.NameNotFoundException e) {
- // Property not found
- }
- return enabled;
- }
-
- @VisibleForTesting
- boolean isCompatFakeFocusEnabled(ActivityInfo info) {
- if (!isCompatFakeFocusEnabledOnDevice()) {
- return false;
- }
- // See if the developer has chosen to opt in / out of treatment
- PackageManager pm = mContext.getPackageManager();
- if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT)) {
- return false;
- } else if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN)) {
- return true;
- }
- if (info.isChangeEnabled(ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)) {
- return true;
- }
- return false;
- }
-
/** Whether fake sending focus is enabled for unfocused apps in splitscreen */
- boolean isCompatFakeFocusEnabledOnDevice() {
+ boolean isCompatFakeFocusEnabled() {
return mIsCompatFakeFocusEnabled
&& DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
@@ -1118,15 +1090,8 @@
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
- return mIsCameraCompatTreatmentEnabled
- && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
- }
-
- // TODO(b/262977416): Cache a runtime flag and implement
- // DeviceConfig.OnPropertiesChangedListener
- private static boolean isCameraCompatTreatmentAllowed() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- "enable_compat_camera_treatment", true);
+ return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
+ || mDeviceConfig.getFlag(KEY_ENABLE_CAMERA_COMPAT_TREATMENT));
}
/** Whether camera compatibility refresh is enabled. */
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index 3f067e3..d004fa6 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -33,6 +33,9 @@
final class LetterboxConfigurationDeviceConfig
implements DeviceConfig.OnPropertiesChangedListener {
+ static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment";
+ private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
+
static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
"enable_display_rotation_immersive_app_compat_policy";
private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
@@ -44,12 +47,19 @@
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
+ KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
+ DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
);
+ // Whether camera compatibility treatment is enabled.
+ // See DisplayRotationCompatPolicy for context.
+ private boolean mIsCameraCompatTreatmentEnabled =
+ DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT;
+
// Whether enabling rotation compat policy for immersive apps that prevents auto rotation
// into non-optimal screen orientation while in fullscreen. This is needed because immersive
// apps, such as games, are often not optimized for all orientations and can have a poor UX
@@ -101,6 +111,8 @@
*/
boolean getFlag(String key) {
switch (key) {
+ case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+ return mIsCameraCompatTreatmentEnabled;
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
@@ -116,6 +128,10 @@
throw new AssertionError("Haven't found default value for flag: " + key);
}
switch (key) {
+ case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+ mIsCameraCompatTreatmentEnabled =
+ getDeviceConfig(key, defaultValue);
+ break;
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
getDeviceConfig(key, defaultValue);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c5a50ca..8d6de58 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,8 +21,10 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -41,6 +43,7 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -136,6 +139,8 @@
private final boolean mIsOverrideToNosensorOrientationEnabled;
// Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE
private final boolean mIsOverrideToReverseLandscapeOrientationEnabled;
+ // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA
+ private final boolean mIsOverrideOrientationOnlyForCameraEnabled;
// Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION
private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled;
@@ -149,6 +154,9 @@
// Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION
private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled;
+ // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS
+ private final boolean mIsOverrideEnableCompatFakeFocusEnabled;
+
@Nullable
private final Boolean mBooleanPropertyAllowOrientationOverride;
@Nullable
@@ -204,6 +212,9 @@
@Nullable
private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
+ @Nullable
+ private final Boolean mBooleanPropertyFakeFocus;
+
private boolean mIsRelauchingAfterRequestedOrientationChanged;
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
@@ -218,6 +229,10 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ mBooleanPropertyFakeFocus =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ mLetterboxConfiguration::isCompatFakeFocusEnabled,
+ PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
mBooleanPropertyCameraCompatAllowForceRotation =
readComponentProperty(packageManager, mActivityRecord.packageName,
() -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
@@ -253,6 +268,8 @@
isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE);
mIsOverrideToNosensorOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR);
+ mIsOverrideOrientationOnlyForCameraEnabled =
+ isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION);
@@ -265,6 +282,9 @@
mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+
+ mIsOverrideEnableCompatFakeFocusEnabled =
+ isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS);
}
/**
@@ -360,6 +380,25 @@
}
/**
+ * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
+ * because some game engines wait to get focus before drawing the content of the app which isn't
+ * guaranteed by default in multi-window modes.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Component property is NOT set to false
+ * <li>Component property is set to true or per-app override is enabled
+ * </ul>
+ */
+ boolean shouldSendFakeFocus() {
+ return shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ mLetterboxConfiguration::isCompatFakeFocusEnabled,
+ mIsOverrideEnableCompatFakeFocusEnabled,
+ mBooleanPropertyFakeFocus);
+ }
+
+ /**
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
@@ -410,6 +449,14 @@
return candidate;
}
+ DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
+ && (displayContent.mDisplayRotationCompatPolicy == null
+ || !displayContent.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+ return candidate;
+ }
+
if (mIsOverrideToReverseLandscapeOrientationEnabled
&& (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
@@ -439,6 +486,10 @@
return candidate;
}
+ boolean isOverrideOrientationOnlyForCameraEnabled() {
+ return mIsOverrideOrientationOnlyForCameraEnabled;
+ }
+
/**
* Whether activity is eligible for activity "refresh" after camera compat force rotation
* treatment. See {@link DisplayRotationCompatPolicy} for context.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index db88f0f..b5df3e0 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -799,6 +799,10 @@
if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
// It also invokes kill().
mDisplayContent.setRotationAnimation(null);
+ if (mDisplayContent.mDisplayRotationCompatPolicy != null) {
+ mDisplayContent.mDisplayRotationCompatPolicy
+ .onScreenRotationAnimationFinished();
+ }
} else {
kill();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6eab745..b46a720 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -113,6 +113,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
@@ -365,6 +366,7 @@
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+ private static final int TRACE_MAX_SECTION_NAME_LENGTH = 127;
static final int LAYOUT_REPEAT_THRESHOLD = 4;
@@ -5461,10 +5463,15 @@
case WAITING_FOR_DRAWN_TIMEOUT: {
Runnable callback = null;
- final WindowContainer container = (WindowContainer) msg.obj;
+ final WindowContainer<?> container = (WindowContainer<?>) msg.obj;
synchronized (mGlobalLock) {
ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s",
container.mWaitingForDrawn);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+ traceEndWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+ }
+ }
container.mWaitingForDrawn.clear();
callback = mWaitingForDrawnCallbacks.remove(container);
}
@@ -6080,10 +6087,16 @@
// Window has been removed or hidden; no draw will now happen, so stop waiting.
ProtoLog.w(WM_DEBUG_SCREEN_ON, "Aborted waiting for drawn: %s", win);
container.mWaitingForDrawn.remove(win);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ traceEndWaitingForWindowDrawn(win);
+ }
} else if (win.hasDrawn()) {
// Window is now drawn (and shown).
ProtoLog.d(WM_DEBUG_SCREEN_ON, "Window drawn win=%s", win);
container.mWaitingForDrawn.remove(win);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ traceEndWaitingForWindowDrawn(win);
+ }
}
}
if (container.mWaitingForDrawn.isEmpty()) {
@@ -6094,6 +6107,22 @@
});
}
+ private void traceStartWaitingForWindowDrawn(WindowState window) {
+ final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+ + window.getWindowTag();
+ final String shortenedTraceName = traceName.substring(0, Math.min(
+ TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+ }
+
+ private void traceEndWaitingForWindowDrawn(WindowState window) {
+ final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+ + window.getWindowTag();
+ final String shortenedTraceName = traceName.substring(0, Math.min(
+ TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+ }
+
void requestTraversal() {
mWindowPlacerLocked.requestTraversal();
}
@@ -7812,7 +7841,7 @@
@Override
public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
- final WindowContainer container = displayId == INVALID_DISPLAY
+ final WindowContainer<?> container = displayId == INVALID_DISPLAY
? mRoot : mRoot.getDisplayContent(displayId);
if (container == null) {
// The waiting container doesn't exist, no need to wait to run the callback. Run and
@@ -7828,6 +7857,12 @@
if (container.mWaitingForDrawn.isEmpty()) {
allWindowsDrawn = true;
} else {
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+ traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+ }
+ }
+
mWaitingForDrawnCallbacks.put(container, callback);
mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
checkDrawnWindowsLocked();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 29d3462..abdc708 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1036,17 +1036,30 @@
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
break;
}
- case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+ case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER: {
final Rect insetsProviderWindowContainer = hop.getInsetsProviderFrame();
- final WindowContainer receiverWindowContainer =
+ final WindowContainer container =
WindowContainer.fromBinder(hop.getContainer());
- receiverWindowContainer.addLocalRectInsetsSourceProvider(
+ if (container == null) {
+ Slog.e(TAG, "Attempt to add local insets source provider on unknown: "
+ + container);
+ break;
+ }
+ container.addLocalRectInsetsSourceProvider(
insetsProviderWindowContainer, hop.getInsetsTypes());
break;
- case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
- WindowContainer.fromBinder(hop.getContainer())
- .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
+ }
+ case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER: {
+ final WindowContainer container =
+ WindowContainer.fromBinder(hop.getContainer());
+ if (container == null) {
+ Slog.e(TAG, "Attempt to remove local insets source provider from unknown: "
+ + container);
+ break;
+ }
+ container.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
+ }
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
if (container == null || container.asDisplayArea() == null
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 97dbe04..ab1badb 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -208,11 +208,7 @@
<xs:complexType name="thermalThrottling">
<xs:sequence>
- <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap">
- <xs:annotation name="nonnull"/>
- <xs:annotation name="final"/>
- </xs:element>
- <xs:element type="brightnessThrottlingMap" name="concurrentDisplaysBrightnessThrottlingMap">
+ <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
@@ -225,6 +221,7 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
+ <xs:attribute name="id" type="xs:string"/>
</xs:complexType>
<xs:complexType name="brightnessThrottlingPoint">
@@ -472,6 +469,14 @@
</xs:complexType>
<xs:complexType name="refreshRateConfigs">
+ <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="defaultPeakRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index aba8a2c..3e1db4c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -37,6 +37,8 @@
public class BrightnessThrottlingMap {
ctor public BrightnessThrottlingMap();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessThrottlingPoint> getBrightnessThrottlingPoint();
+ method public String getId();
+ method public void setId(String);
}
public class BrightnessThrottlingPoint {
@@ -188,8 +190,12 @@
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
+ method public final java.math.BigInteger getDefaultPeakRefreshRate();
+ method public final java.math.BigInteger getDefaultRefreshRate();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+ method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
+ method public final void setDefaultRefreshRate(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
}
@@ -238,10 +244,7 @@
public class ThermalThrottling {
ctor public ThermalThrottling();
- method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap();
- method public final com.android.server.display.config.BrightnessThrottlingMap getConcurrentDisplaysBrightnessThrottlingMap();
- method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
- method public final void setConcurrentDisplaysBrightnessThrottlingMap(com.android.server.display.config.BrightnessThrottlingMap);
+ method public final java.util.List<com.android.server.display.config.BrightnessThrottlingMap> getBrightnessThrottlingMap();
}
public class ThresholdPoint {
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index 842d97a..78c9a54 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -51,6 +51,7 @@
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 55f866c..6a28d8a 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,10 +4,12 @@
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
+ method public String getBrightnessThrottlingMapId();
method public String getPosition();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
+ method public void setBrightnessThrottlingMapId(String);
method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
method public void setPosition(String);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index bb26fa9..d9a6d54 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -49,8 +49,6 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.service.credentials.BeginCreateCredentialRequest;
-import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
import android.text.TextUtils;
@@ -364,11 +362,7 @@
}
// Iterate over all provider sessions and invoke the request
- providerSessions.forEach(
- providerGetSession -> providerGetSession
- .getRemoteCredentialService().onBeginGetCredential(
- (BeginGetCredentialRequest) providerGetSession.getProviderRequest(),
- /*callback=*/providerGetSession));
+ providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@@ -412,12 +406,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
- providerCreateSession -> providerCreateSession
- .getRemoteCredentialService()
- .onCreateCredential(
- (BeginCreateCredentialRequest)
- providerCreateSession.getProviderRequest(),
- /* callback= */ providerCreateSession));
+ ProviderSession::invokeSession);
return cancelTransport;
}
@@ -530,14 +519,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
- providerClearSession -> {
- providerClearSession
- .getRemoteCredentialService()
- .onClearCredentialState(
- (android.service.credentials.ClearCredentialStateRequest)
- providerClearSession.getProviderRequest(),
- /* callback= */ providerClearSession);
- });
+ ProviderSession::invokeSession);
return cancelTransport;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 48e35b2..b112649 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -115,4 +115,11 @@
ProviderPendingIntentResponse providerPendingIntentResponse) {
//Not applicable for clearCredential as response is not picked by the user
}
+
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onClearCredentialState(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 7a24a22..cc5a8ab 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -197,6 +197,13 @@
}
}
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onCreateCredential(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
+
private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 95f2313..b4058ed 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -230,6 +230,13 @@
}
}
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onBeginGetCredential(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
+
@Override // Call from request session to data to be shown on the UI
@Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 678c752..8e0d6f8 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -208,4 +208,8 @@
/** Should be overridden to handle the selected entry from the UI. */
protected abstract void onUiEntrySelected(String entryType, String entryId,
ProviderPendingIntentResponse providerPendingIntentResponse);
+
+ /** Should be overridden to invoke the provider at a defined location. Helpful for
+ * situations such as metric generation. */
+ protected abstract void invokeSession();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 41ae50b..5e4ba88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1401,10 +1401,10 @@
// Confirm that we saw no registered receiver traffic
final IApplicationThread oldThread = oldApp.getThread();
verify(oldThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
final IApplicationThread newThread = newApp.getThread();
verify(newThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
// Confirm that we saw final manifest broadcast
verifyScheduleReceiver(times(1), newApp, airplane,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2fb6c23..9f7b72c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -630,6 +630,26 @@
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
}
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_ImpFg_AlmostPerceptibleService() {
+ ProcessRecord system = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
+ system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ system.mState.setHasTopUi(true);
+ // Simulate the system starting and binding to a service in the app.
+ ServiceRecord s = bindService(app, system,
+ null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+ PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoOne_Toast() {
@@ -1056,13 +1076,15 @@
@SuppressWarnings("GuardedBy")
@Test
- public void testUpdateOomAdj_DoOne_Service_MediumPerceptible() {
+ public void testUpdateOomAdj_DoOne_Service_AlmostPerceptible() {
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
+ mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
@@ -1077,7 +1099,9 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
WindowProcessController wpc = client.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
- bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
+ mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
@@ -1085,6 +1109,39 @@
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
}
+
+ {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE,
+ mock(IBinder.class));
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+ }
+
+ {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ WindowProcessController wpc = client.getWindowProcessController();
+ doReturn(true).when(wpc).isHeavyWeightProcess();
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE,
+ mock(IBinder.class));
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ doReturn(false).when(wpc).isHeavyWeightProcess();
+
+ assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+ }
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 3d36c1c..57e873d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -16,13 +16,17 @@
package com.android.server.display;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -46,8 +50,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -58,7 +65,9 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -67,8 +76,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerController2Test {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
- private static final int DISPLAY_ID = 42;
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
@@ -105,13 +115,20 @@
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
@Mock
private WakelockController mWakelockController;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(LocalServices.class)
+ .spyStatic(BatteryStatsService.class)
+ .startMocking();
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -156,10 +173,17 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+ doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+ mCdsiMock).when(() -> LocalServices.getService(
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ BatteryStatsService.getService());
}
@After
public void tearDown() {
+ mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
}
@@ -207,6 +231,32 @@
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
}
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
+ setUpDisplay(1, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController2 dpc = new DisplayPowerController2(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(proxSensor), anyInt(), any(Handler.class));
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index b6388cc..6bf5b62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -16,14 +16,18 @@
package com.android.server.display;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -46,8 +50,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -58,7 +65,9 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -67,8 +76,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerControllerTest {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
- private static final int DISPLAY_ID = 42;
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
@@ -103,13 +113,20 @@
private DisplayPowerState mDisplayPowerStateMock;
@Mock
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(LocalServices.class)
+ .spyStatic(BatteryStatsService.class)
+ .startMocking();
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -138,10 +155,17 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+ doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+ mCdsiMock).when(() -> LocalServices.getService(
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ BatteryStatsService.getService());
}
@After
public void tearDown() {
+ mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
}
@@ -189,6 +213,32 @@
dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
}
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
+ setUpDisplay(1, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController dpc = new DisplayPowerController(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(proxSensor), anyInt(), any(Handler.class));
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index bcae50e..a192913 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -64,10 +64,12 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
- /* isEnabled= */ false, mDisplayIdProducerMock);
+ /* isEnabled= */ false, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
assertEquals(testLayout, configLayout);
}
@@ -78,10 +80,12 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
- /* isEnabled= */ false, mDisplayIdProducerMock);
+ /* isEnabled= */ false, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
assertEquals(testLayout, configLayout);
}
@@ -91,16 +95,30 @@
Layout configLayout = mDeviceStateToLayoutMap.get(2);
Layout testLayout = new Layout();
- testLayout.createDisplayLocked(
+
+ Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
- testLayout.createDisplayLocked(
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ "concurrent");
+ display1.setPosition(Layout.Display.POSITION_FRONT);
+
+ Layout.Display display2 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ "concurrent");
+ display2.setPosition(Layout.Display.POSITION_REAR);
assertEquals(testLayout, configLayout);
}
+ @Test
+ public void testRearDisplayLayout() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+ assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
+ assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -139,9 +157,13 @@
+ "<state>2</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ "<address>345</address>\n"
+ + "<position>front</position>\n"
+ + "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "<display enabled=\"true\">\n"
+ "<address>678</address>\n"
+ + "<position>rear</position>\n"
+ + "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 0ea20a8..42bdbec 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -53,6 +53,8 @@
public final class DisplayDeviceConfigTest {
private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
private static final int DEFAULT_REFRESH_RATE = 120;
+ private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
+ private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
@@ -149,8 +151,10 @@
assertArrayEquals(new float[]{23, 24, 25},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
- assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate());
- assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate());
+ assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
+ assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
+ assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
+ assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
assertArrayEquals(new int[]{45, 55},
mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
assertArrayEquals(new int[]{50, 60},
@@ -189,7 +193,7 @@
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
));
assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData("default"));
throttlingLevels.clear();
throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
@@ -211,7 +215,7 @@
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
));
assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getConcurrentDisplaysBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData("concurrent"));
assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
@@ -278,8 +282,12 @@
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
assertArrayEquals(new float[]{29, 30, 31},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
- assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE);
- assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
+ DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
+ DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -509,7 +517,7 @@
+ "<brightness>0.025</brightness>\n"
+ "</brightnessThrottlingPoint>\n"
+ "</brightnessThrottlingMap>\n"
- + "<concurrentDisplaysBrightnessThrottlingMap>\n"
+ + "<brightnessThrottlingMap id=\"concurrent\">\n"
+ "<brightnessThrottlingPoint>\n"
+ "<thermalStatus>light</thermalStatus>\n"
+ "<brightness>0.2</brightness>\n"
@@ -534,9 +542,11 @@
+ "<thermalStatus>shutdown</thermalStatus>\n"
+ "<brightness>0.0125</brightness>\n"
+ "</brightnessThrottlingPoint>\n"
- + "</concurrentDisplaysBrightnessThrottlingMap>\n"
+ + "</brightnessThrottlingMap>\n"
+ "</thermalThrottling>\n"
+ "<refreshRate>\n"
+ + "<defaultRefreshRate>45</defaultRefreshRate>\n"
+ + "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+ "<lowerBlockingZoneConfigs>\n"
+ "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ "<blockingZoneThreshold>\n"
@@ -642,10 +652,14 @@
.thenReturn(new int[]{370, 380, 390});
// Configs related to refresh rates and blocking zones
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
.thenReturn(DEFAULT_PEAK_REFRESH_RATE);
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(DEFAULT_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index a35f4ea..27d912b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -2260,6 +2260,10 @@
.thenReturn(75);
when(resources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(45);
+ when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(65);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(85);
when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
@@ -2279,6 +2283,8 @@
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{250});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2290,17 +2296,21 @@
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50);
- when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
+ when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
- assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{210});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2313,6 +2323,8 @@
// Notify that the default display is updated, such that DeviceConfig has new values
FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setDefaultPeakRefreshRate(60);
+ config.setRefreshRateInHighZone(65);
+ config.setRefreshRateInLowZone(70);
config.setLowAmbientBrightnessThresholds(new int[]{20});
config.setLowDisplayBrightnessThresholds(new int[]{10});
config.setHighDisplayBrightnessThresholds(new int[]{255});
@@ -2320,9 +2332,11 @@
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{255});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2362,8 +2376,8 @@
any(Handler.class));
DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
- when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
- when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+ when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index a860387..8a37ed9 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -19,6 +19,7 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.FLAG_REAR;
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.Display.TYPE_VIRTUAL;
@@ -30,6 +31,8 @@
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
@@ -296,9 +299,9 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
assertThat(layout1.size()).isEqualTo(2);
final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -331,17 +334,17 @@
add(device3);
Layout layout1 = new Layout();
- layout1.createDisplayLocked(info(device1).address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
final int layoutState2 = 2;
Layout layout2 = new Layout();
- layout2.createDisplayLocked(info(device2).address,
- /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+ layout2.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
// Device3 is the default display.
- layout2.createDisplayLocked(info(device3).address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ layout2.createDisplayLocked(info(device3).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
assertThat(layout2.size()).isEqualTo(2);
final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -563,16 +566,18 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer);
+ true, true, mIdProducer,
+ /* brightnessThrottlingMapId= */ "concurrent");
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- false, true, mIdProducer);
+ false, true, mIdProducer,
+ /* brightnessThrottlingMapId= */ "concurrent");
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- false, false, mIdProducer);
+ false, false, mIdProducer, /* brightnessThrottlingMapId= */ null);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -599,6 +604,10 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(1, false);
advanceTime(1000);
@@ -606,6 +615,12 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(2, false);
advanceTime(1000);
@@ -613,6 +628,12 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
}
@Test
@@ -633,17 +654,20 @@
displayAddressOne,
/* isDefault= */ true,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -678,17 +702,20 @@
displayAddressOne,
/* isDefault= */ true,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ false,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ false,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
@@ -754,6 +781,45 @@
assertFalse(display2.isEnabledLocked());
}
+ @Test
+ public void testDisplayFlagRear() {
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_REAR);
+
+ Layout layout = new Layout();
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ false, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ POSITION_REAR);
+ when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+
+ LogicalDisplay display1 = add(device1);
+ assertEquals(info(display1).address, info(device1).address);
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ LogicalDisplay display2 = add(device2);
+ assertEquals(info(display2).address, info(device2).address);
+ // We can only have one default display
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ advanceTime(1000);
+ mLogicalDisplayMapper.onBootCompleted();
+ advanceTime(1000);
+
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+ assertEquals(POSITION_UNKNOWN,
+ mLogicalDisplayMapper.getDisplayLocked(device1).getPositionLocked());
+ assertEquals(POSITION_REAR,
+ mLogicalDisplayMapper.getDisplayLocked(device2).getPositionLocked());
+ }
/////////////////
// Helper Methods
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
new file mode 100644
index 0000000..52c6777
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.IBinder;
+import android.os.IDumpstateListener;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BugreportManagerServiceImplTest {
+
+ Context mContext;
+ BugreportManagerServiceImpl mService;
+ BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+
+ int mCallingUid = 1234;
+ String mCallingPackage = "test.package";
+
+ String mBugreportFile = "bugreport-file.zip";
+ String mBugreportFile2 = "bugreport-file2.zip";
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ ArraySet<String> mAllowlistedPackages = new ArraySet<>();
+ mAllowlistedPackages.add(mContext.getPackageName());
+ mService = new BugreportManagerServiceImpl(
+ new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages));
+ mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager();
+ }
+
+ @Test
+ public void testBugreportFileManagerFileExists() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "unknown-file.zip"));
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ }
+
+ @Test
+ public void testBugreportFileManagerMultipleFiles() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile2);
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+ }
+
+ @Test
+ public void testBugreportFileManagerFileDoesNotExist() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "test-file.zip"));
+ }
+
+ @Test
+ public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ Listener listener = new Listener(latch);
+ mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
+ new FileDescriptor(), mBugreportFile, listener);
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(listener.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ }
+
+ private static class Listener implements IDumpstateListener {
+ CountDownLatch mLatch;
+ int mErrorCode;
+
+ Listener(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void onProgress(int progress) throws RemoteException {
+ }
+
+ @Override
+ public void onError(int errorCode) throws RemoteException {
+ mErrorCode = errorCode;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onFinished(String bugreportFile) throws RemoteException {
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onScreenshotTaken(boolean success) throws RemoteException {
+ }
+
+ @Override
+ public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/os/OWNERS b/services/tests/servicestests/src/com/android/server/os/OWNERS
new file mode 100644
index 0000000..feb8011
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/OWNERS
@@ -0,0 +1,2 @@
+# Component 153446
+include /platform/frameworks/native:/cmds/dumpstate/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
new file mode 100644
index 0000000..9902446
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "BugreportManagerServiceImplTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.os."
+ }
+ ]
+ }
+ ]
+}
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 20e8cd4..23587c9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -19,7 +19,6 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
@@ -218,7 +217,6 @@
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -307,9 +305,6 @@
ActivityManager mActivityManager;
@Mock
TelecomManager mTelecomManager;
-
- @Mock
- PermissionManagerServiceInternal mPermissionInternal;
@Mock
Resources mResources;
@Mock
@@ -523,7 +518,7 @@
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
- mTelecomManager, mLogger, mPermissionInternal);
+ mTelecomManager, mLogger);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -1651,6 +1646,46 @@
}
@Test
+ public void testEnqueueNotificationWithTag_nullAction_fixed() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "one", null).build())
+ .addAction(new Notification.Action.Builder(null, "two", null).build())
+ .addAction(new Notification.Action.Builder(null, "three", null).build())
+ .build();
+ n.actions[1] = null;
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ waitForIdle();
+
+ StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+ assertThat(posted).hasLength(1);
+ assertThat(posted[0].getNotification().actions).hasLength(2);
+ assertThat(posted[0].getNotification().actions[0].title.toString()).isEqualTo("one");
+ assertThat(posted[0].getNotification().actions[1].title.toString()).isEqualTo("three");
+ }
+
+ @Test
+ public void testEnqueueNotificationWithTag_allNullActions_fixed() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "one", null).build())
+ .addAction(new Notification.Action.Builder(null, "two", null).build())
+ .build();
+ n.actions[0] = null;
+ n.actions[1] = null;
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ waitForIdle();
+
+ StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+ assertThat(posted).hasLength(1);
+ assertThat(posted[0].getNotification().actions).isNull();
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4173,35 +4208,8 @@
}
@Test
- public void testNoNotificationDuringSetupPermission() throws Exception {
- when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
- .thenReturn(PERMISSION_DENIED);
- Bundle extras = new Bundle();
- extras.putBoolean(EXTRA_ALLOW_DURING_SETUP, true);
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .addExtras(extras)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testNoNotificationDuringSetupPermission", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- NotificationRecord posted = mService.findNotificationLocked(
- PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
-
- assertFalse(posted.getNotification().extras.containsKey(EXTRA_ALLOW_DURING_SETUP));
- }
-
- @Test
public void testNoFakeColorizedPermission() throws Exception {
- when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
- .thenReturn(PERMISSION_DENIED);
+ when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED);
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setContentTitle("foo")
@@ -4226,7 +4234,7 @@
@Test
public void testMediaStyleRemote_hasPermission() throws RemoteException {
String deviceName = "device";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4255,7 +4263,7 @@
@Test
public void testMediaStyleRemote_noPermission() throws RemoteException {
String deviceName = "device";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
.thenReturn(PERMISSION_DENIED);
Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4283,7 +4291,7 @@
@Test
public void testSubstituteAppName_hasPermission() throws RemoteException {
String subName = "Substitute Name";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
Bundle extras = new Bundle();
@@ -4310,7 +4318,7 @@
@Test
public void testSubstituteAppName_noPermission() throws RemoteException {
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
.thenReturn(PERMISSION_DENIED);
Bundle extras = new Bundle();
@@ -5820,7 +5828,7 @@
@Test
public void testCanUseManagedServices_hasPermission() throws Exception {
- when(mPermissionInternal.checkPermission("perm", "pkg", 0))
+ when(mPackageManager.checkPermission("perm", "pkg", 0))
.thenReturn(PackageManager.PERMISSION_GRANTED);
assertEquals(true, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -5828,7 +5836,7 @@
@Test
public void testCanUseManagedServices_noPermission() throws Exception {
- when(mPermissionInternal.checkPermission("perm", "pkg", 0))
+ when(mPackageManager.checkPermission("perm", "pkg", 0))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertEquals(false, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -6783,7 +6791,7 @@
private void setIfPackageHasPermissionToAvoidToastRateLimiting(
String pkg, boolean hasPermission) throws Exception {
- when(mPermissionInternal.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+ when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
pkg, UserHandle.getUserId(mUid)))
.thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 3a01c2d..98c156e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -72,7 +72,6 @@
import com.android.server.lights.LightsManager;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -169,9 +168,8 @@
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
- mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
- mock(NotificationChannelLogger.class),
- mock(PermissionManagerServiceInternal.class));
+ mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
+ mock(NotificationChannelLogger.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
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 45b30b2..4954e89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+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;
@@ -30,7 +31,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
@@ -58,6 +61,8 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -128,6 +133,69 @@
}
@Test
+ public void testOpenedCameraInSplitScreen_showToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ spyOn(mTask);
+ spyOn(mDisplayRotationCompatPolicy);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ verify(mDisplayRotationCompatPolicy).showToast(
+ R.string.display_rotation_camera_compat_toast_in_split_screen);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ anyBoolean()))
+ .thenReturn(false);
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_noOpenCamera_doNotShowToast() {
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_showToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ anyBoolean()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index ead1a86..12b7c9d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -51,7 +51,7 @@
* Tests for the {@link LetterboxConfiguration} class.
*
* Build/Install/Run:
- * atest WmTests:LetterboxConfigurationTests
+ * atest WmTests:LetterboxConfigurationTest
*/
@SmallTest
@Presubmit
@@ -243,18 +243,18 @@
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
// Set runtime flag to false and build time flag to true
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
// Set runtime flag to true so that both are enabled
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabled());
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
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 110ef89..d8037f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -20,8 +20,10 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -37,6 +39,7 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -71,6 +74,7 @@
import com.android.internal.R;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Before;
@@ -579,6 +583,42 @@
/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+ OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+ assertEquals(mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+ OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+ assertEquals(mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ }
+
// shouldUseDisplayLandscapeNaturalOrientation
@Test
@@ -629,6 +669,72 @@
assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideEnabled_fakeFocusDisabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index b8d31ab..7ad5442 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,8 +16,10 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -56,8 +58,6 @@
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -3654,7 +3654,8 @@
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
- private ActivityRecord setUpActivityForCompatFakeFocusTest() {
+ @Test
+ public void testShouldSendFakeFocus_compatFakeFocusEnabled() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setOnTop(true)
@@ -3663,69 +3664,40 @@
com.android.server.wm.SizeCompatTests.class.getName()))
.build();
final Task task = activity.getTask();
+ spyOn(activity.mLetterboxUiController);
+ doReturn(true).when(activity.mLetterboxUiController).shouldSendFakeFocus();
+
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- spyOn(activity.mWmService.mLetterboxConfiguration);
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .isCompatFakeFocusEnabledOnDevice();
- return activity;
- }
-
- @Test
- @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-
assertTrue(activity.shouldSendCompatFakeFocus());
- }
- @Test
- @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ assertFalse(activity.shouldSendCompatFakeFocus());
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(activity.shouldSendCompatFakeFocus());
}
@Test
- @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_optOutPropertyAndOverrideEnabled_fakeFocusDisabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
+ public void testShouldSendFakeFocus_compatFakeFocusDisabled() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .setOnTop(true)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ com.android.server.wm.SizeCompatTests.class.getName()))
+ .build();
+ final Task task = activity.getTask();
+ spyOn(activity.mLetterboxUiController);
+ doReturn(false).when(activity.mLetterboxUiController).shouldSendFakeFocus();
- assertFalse(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
+ task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ assertFalse(activity.shouldSendCompatFakeFocus());
- @Test
- @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_noOverride_fakeFocusEnabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ assertFalse(activity.shouldSendCompatFakeFocus());
- assertTrue(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_optOutPropertyEnabled_fakeFocusDisabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
-
- assertFalse(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_fakeFocusEnabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
-
- assertTrue(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertFalse(activity.shouldSendCompatFakeFocus());
}
private int getExpectedSplitSize(int dimensionToSplit) {
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 17c354a..e1de58e 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -23,7 +23,9 @@
import android.media.IAudioService;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
+import android.os.FileObserver;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.provider.Settings;
import android.service.usb.UsbAlsaManagerProto;
import android.util.Slog;
@@ -34,9 +36,11 @@
import libcore.io.IoUtils;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
/**
@@ -52,6 +56,11 @@
private static final String ALSA_DIRECTORY = "/dev/snd/";
+ private static final int ALSA_DEVICE_TYPE_UNKNOWN = 0;
+ private static final int ALSA_DEVICE_TYPE_PLAYBACK = 1;
+ private static final int ALSA_DEVICE_TYPE_CAPTURE = 2;
+ private static final int ALSA_DEVICE_TYPE_MIDI = 3;
+
private final Context mContext;
private IAudioService mAudioService;
private final boolean mHasMidiFeature;
@@ -116,6 +125,21 @@
// UsbMidiDevice for USB peripheral mode (gadget) device
private UsbMidiDevice mPeripheralMidiDevice = null;
+ private final HashSet<Integer> mAlsaCards = new HashSet<>();
+ private final FileObserver mAlsaObserver = new FileObserver(new File(ALSA_DIRECTORY),
+ FileObserver.CREATE | FileObserver.DELETE) {
+ public void onEvent(int event, String path) {
+ switch (event) {
+ case FileObserver.CREATE:
+ alsaFileAdded(path);
+ break;
+ case FileObserver.DELETE:
+ alsaFileRemoved(path);
+ break;
+ }
+ }
+ };
+
/* package */ UsbAlsaManager(Context context) {
mContext = context;
mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
@@ -124,6 +148,7 @@
public void systemReady() {
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
+ mAlsaObserver.startWatching();
}
/**
@@ -224,6 +249,8 @@
return;
}
+ waitForAlsaDevice(cardRec.getCardNum(), true /*isAdded*/);
+
// Add it to the devices list
boolean hasInput = parser.hasInput()
&& !isDeviceDenylisted(usbDevice.getVendorId(), usbDevice.getProductId(),
@@ -322,6 +349,7 @@
UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+ waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
deselectAlsaDevice();
selectDefaultDevice(); // if there any external devices left, select one of them
}
@@ -361,6 +389,89 @@
}
}
+ private boolean waitForAlsaDevice(int card, boolean isAdded) {
+ if (DEBUG) {
+ Slog.e(TAG, "waitForAlsaDevice(c:" + card + ")");
+ }
+
+ // This value was empirically determined.
+ final int kWaitTimeMs = 2500;
+
+ synchronized (mAlsaCards) {
+ long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs;
+ while ((isAdded ^ mAlsaCards.contains(card))
+ && timeoutMs > SystemClock.elapsedRealtime()) {
+ long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime();
+ if (waitTimeMs > 0) {
+ try {
+ mAlsaCards.wait(waitTimeMs);
+ } catch (InterruptedException e) {
+ Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
+ }
+ }
+ }
+ final boolean cardFound = mAlsaCards.contains(card);
+ if ((isAdded ^ cardFound) && timeoutMs > SystemClock.elapsedRealtime()) {
+ Slog.e(TAG, "waitForAlsaDevice(" + card + ") timeout");
+ } else {
+ Slog.i(TAG, "waitForAlsaDevice for device card=" + card + ", isAdded=" + isAdded
+ + ", found=" + cardFound);
+ }
+ return cardFound;
+ }
+ }
+
+ private int getCardNumberFromAlsaFilePath(String path) {
+ int type = ALSA_DEVICE_TYPE_UNKNOWN;
+ if (path.startsWith("pcmC")) {
+ if (path.endsWith("p")) {
+ type = ALSA_DEVICE_TYPE_PLAYBACK;
+ } else if (path.endsWith("c")) {
+ type = ALSA_DEVICE_TYPE_CAPTURE;
+ }
+ } else if (path.startsWith("midiC")) {
+ type = ALSA_DEVICE_TYPE_MIDI;
+ }
+
+ if (type == ALSA_DEVICE_TYPE_UNKNOWN) {
+ Slog.i(TAG, "Unknown type file(" + path + ") added.");
+ return -1;
+ }
+ try {
+ int c_index = path.indexOf('C');
+ int d_index = path.indexOf('D');
+ return Integer.parseInt(path.substring(c_index + 1, d_index));
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not parse ALSA file name " + path, e);
+ return -1;
+ }
+ }
+
+ private void alsaFileAdded(String path) {
+ Slog.i(TAG, "alsaFileAdded(" + path + ")");
+ final int card = getCardNumberFromAlsaFilePath(path);
+ if (card == -1) {
+ return;
+ }
+ synchronized (mAlsaCards) {
+ if (!mAlsaCards.contains(card)) {
+ Slog.d(TAG, "Adding ALSA device card=" + card);
+ mAlsaCards.add(card);
+ mAlsaCards.notifyAll();
+ }
+ }
+ }
+
+ private void alsaFileRemoved(String path) {
+ final int card = getCardNumberFromAlsaFilePath(path);
+ if (card == -1) {
+ return;
+ }
+ synchronized (mAlsaCards) {
+ mAlsaCards.remove(card);
+ }
+ }
+
//
// Devices List
//
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 315ac67..f3c91f6 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -28,6 +28,7 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
@@ -67,7 +68,7 @@
/**
* @return the callId Telecom assigned to this CallControl object which should be attached to
- * an individual call.
+ * an individual call.
*/
@NonNull
public ParcelUuid getCallId() {
@@ -78,9 +79,9 @@
* Request Telecom set the call state to active.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
+ * will be called on.
* @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
* switched the call state to active
@@ -109,9 +110,9 @@
* but can be extended to setting a meeting to inactive.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
+ * will be called on.
* @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
* switched the call state to inactive
@@ -136,23 +137,42 @@
}
/**
- * Request Telecom set the call state to disconnect.
+ * Request Telecom disconnect the call and remove the call from telecom tracking.
*
- * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
- * @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * @param disconnectCause represents the cause for disconnecting the call. The only valid
+ * codes for the {@link android.telecom.DisconnectCause} passed in are:
+ * <ul>
+ * <li>{@link DisconnectCause#LOCAL}</li>
+ * <li>{@link DisconnectCause#REMOTE}</li>
+ * <li>{@link DisconnectCause#REJECTED}</li>
+ * <li>{@link DisconnectCause#MISSED}</li>
+ * </ul>
*
- * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
- * disconnected the call.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
*
- * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
- * disconnect the call. A {@link CallException} will be passed
- * that details why the operation failed.
+ * @param callback That will be completed on the Telecom side that details success or
+ * failure of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has
+ * successfully disconnected the call.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed
+ * to disconnect the call. A {@link CallException} will be passed
+ * that details why the operation failed.
+ *
+ * <p>
+ * Note: After the call has been successfully disconnected, calling any CallControl API will
+ * result in the {@link OutcomeReceiver#onError} with
+ * {@link CallException#CODE_CALL_IS_NOT_BEING_TRACKED}.
*/
public void disconnect(@NonNull DisconnectCause disconnectCause,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
+ Objects.requireNonNull(disconnectCause);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ validateDisconnectCause(disconnectCause);
if (mServerInterface != null) {
try {
mServerInterface.disconnect(mCallId, disconnectCause,
@@ -166,35 +186,6 @@
}
/**
- * Request Telecom reject the incoming call.
- *
- * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
- * @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
- *
- * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
- * rejected the incoming call.
- *
- * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
- * reject the incoming call. A {@link CallException} will be passed
- * that details why the operation failed.
- */
- public void rejectCall(@CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.rejectCall(mCallId,
- new CallControlResultReceiver("rejectCall", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
- }
- }
-
- /**
* Request start a call streaming session. On receiving valid request, telecom will bind to
* the {@link CallStreamingService} implemented by a general call streaming sender. So that the
* call streaming sender can perform streaming local device audio to another remote device and
@@ -231,10 +222,10 @@
* requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
* by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
*
- * @param callEndpoint ; The {@link CallEndpoint} to change to.
- * @param executor ; The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * @param callEndpoint The {@link CallEndpoint} to change to.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
* will be called on.
- * @param callback ; The {@link OutcomeReceiver} that will be completed on the Telecom side
+ * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
* that details success or failure of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has
@@ -266,7 +257,9 @@
* Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
* wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
* response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
- * @hide */
+ *
+ * @hide
+ */
private class CallControlResultReceiver extends ResultReceiver {
private final String mCallingMethod;
private final Executor mExecutor;
@@ -308,4 +301,18 @@
}
return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
}
+
+ /** @hide */
+ private void validateDisconnectCause(DisconnectCause disconnectCause) {
+ final int code = disconnectCause.getCode();
+ if (code != DisconnectCause.LOCAL && code != DisconnectCause.REMOTE
+ && code != DisconnectCause.MISSED && code != DisconnectCause.REJECTED) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The DisconnectCause code provided, %d , is not a valid Disconnect code. Valid "
+ + "DisconnectCause codes are limited to [DisconnectCause.LOCAL, "
+ + "DisconnectCause.REMOTE, DisconnectCause.MISSED, or "
+ + "DisconnectCause.REJECTED]", disconnectCause.getCode()));
+ }
+ }
+
}
diff --git a/telecomm/java/android/telecom/CallException.java b/telecomm/java/android/telecom/CallException.java
index 0b0de6b..d191593 100644
--- a/telecomm/java/android/telecom/CallException.java
+++ b/telecomm/java/android/telecom/CallException.java
@@ -116,17 +116,6 @@
}
/**
- * Constructor for a new CallException when only message can be specified.
- * {@code CODE_ERROR_UNKNOWN} will be default code returned when calling {@code getCode}
- *
- * @param message related to why the exception was created
- */
- public CallException(@Nullable String message) {
- super(getMessage(message, CODE_ERROR_UNKNOWN));
- mMessage = message;
- }
-
- /**
* Constructor for a new CallException that has a defined error code in this class
*
* @param message related to why the exception was created
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index a5c6e44..b78a77e 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -28,7 +28,6 @@
void setActive(String callId, in ResultReceiver callback);
void setInactive(String callId, in ResultReceiver callback);
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
- void rejectCall(String callId, in ResultReceiver callback);
void startCallStreaming(String callId, in ResultReceiver callback);
void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
}
\ No newline at end of file
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index d4a8600..8662359 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -20,6 +20,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -859,7 +860,7 @@
if ((subManager != null) &&
(!subManager.isSubscriptionAssociatedWithUser(subId, callerUserHandle))) {
// If subId is not associated with calling user, return false.
- Log.e(LOG_TAG,"User[User ID:" + callerUserHandle.getIdentifier()
+ Log.e(LOG_TAG, "User[User ID:" + callerUserHandle.getIdentifier()
+ "] is not associated with Subscription ID:" + subId);
return false;
@@ -869,4 +870,27 @@
}
return true;
}
+
+ /**
+ * Ensure the caller (or self, if not processing an IPC) has
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
+ * {@link android.Manifest.permission#READ_PHONE_NUMBERS}.
+ *
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.READ_PHONE_NUMBERS,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
+ })
+ public static boolean checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
+ Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
+ String message) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ return false;
+ }
+ return (context.checkCallingOrSelfPermission(
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PERMISSION_GRANTED
+ || checkCallingOrSelfReadPhoneNumber(context, subId, callingPackage,
+ callingFeatureId, message));
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c65faea..c238f24 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6000,6 +6000,7 @@
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
* @hide
+ * @deprecated use {@link #getImsPrivateUserIdentity()}
*/
@UnsupportedAppUsage
public String getIsimImpi() {
@@ -6018,6 +6019,35 @@
}
/**
+ * Returns the IMS private user identity (IMPI) of the subscription that was loaded from the
+ * ISIM records {@link #APPTYPE_ISIM}. This value is fetched from the Elementary file EF_IMPI.
+ * The contents of the file is a <b>Ip Multimedia Service Private User Identity</b> of the user
+ * as defined in the section 4.2.2 of 3GPP TS 131 103.
+ *
+ * @return IMPI (IMS private user identity) of type string.
+ * @throws IllegalStateException in case the ISIM has’t been loaded
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public String getImsPrivateUserIdentity() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ Rlog.e(TAG, "getImsPrivateUserIdentity(): IPhoneSubInfo instance is NULL");
+ throw new RuntimeException("IMPI error: Subscriber Info is null");
+ }
+ return info.getImsPrivateUserIdentity(getSubId(), getOpPackageName(),
+ getAttributionTag());
+ } catch (RemoteException | NullPointerException | IllegalArgumentException ex) {
+ Rlog.e(TAG, "getImsPrivateUserIdentity() Exception = " + ex);
+ throw new RuntimeException(ex.getMessage());
+ }
+ }
+
+ /**
* Returns the IMS home network domain name that was loaded from the ISIM {@see #APPTYPE_ISIM}.
* @return the IMS domain name. Returns {@code null} if ISIM hasn't been loaded or IMS domain
* hasn't been loaded or isn't present on the ISIM.
@@ -6050,6 +6080,7 @@
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
* @hide
+ * @deprecated use {@link #getImsPublicUserIdentities()}
*/
@UnsupportedAppUsage
@Nullable
@@ -6070,6 +6101,39 @@
}
/**
+ * Returns the IMS public user identities (IMPU) of the subscription that was loaded from the
+ * ISIM records {@link #APPTYPE_ISIM}. This value is fetched from the Elementary file EF_IMPU.
+ * The contents of the file are <b>Ip Multimedia Service Public User Identities</b> of the user
+ * as defined in the section 4.2.4 of 3GPP TS 131 103. It contains one or more records.
+ *
+ * @return List of public user identities of type android.net.Uri or empty list if
+ * EF_IMPU is not available.
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded
+ * @throws SecurityException if the caller does not have the required permission/privilege
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_NUMBERS,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public List<Uri> getImsPublicUserIdentities() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ throw new RuntimeException("IMPU error: Subscriber Info is null");
+ }
+ return info.getImsPublicUserIdentities(getSubId(), getOpPackageName(),
+ getAttributionTag());
+ } catch (IllegalArgumentException | NullPointerException ex) {
+ Rlog.e(TAG, "getImsPublicUserIdentities Exception = " + ex);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getImsPublicUserIdentities Exception = " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return Collections.EMPTY_LIST;
+ }
+
+ /**
* Device call state: No activity.
*/
public static final int CALL_STATE_IDLE = 0;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index d015427..7a60c58 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -179,7 +179,6 @@
* Error received from satellite service.
*/
public static final int SATELLITE_SERVICE_ERROR = 17;
-
/**
* Satellite service is disabled on the requested subscription.
*/
@@ -275,6 +274,9 @@
@SatelliteServiceResult public int startSatellitePositionUpdates(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatellitePositionUpdateCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -290,16 +292,16 @@
public void onSatellitePositionUpdate(
@NonNull PointingInfo pointingInfo) {
logd("onSatellitePositionUpdate: pointingInfo=" + pointingInfo);
- executor.execute(() ->
- callback.onSatellitePositionUpdate(pointingInfo));
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatellitePositionUpdate(pointingInfo)));
}
@Override
public void onMessageTransferStateUpdate(
@SatelliteMessageTransferState int state) {
logd("onMessageTransferStateUpdate: state=" + state);
- executor.execute(() ->
- callback.onMessageTransferStateUpdate(state));
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onMessageTransferStateUpdate(state)));
}
});
if (result == SATELLITE_SERVICE_SUCCESS) {
@@ -330,6 +332,8 @@
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteServiceResult public int stopSatellitePositionUpdates(
@NonNull SatellitePositionUpdateCallback callback) {
+ Objects.requireNonNull(callback);
+
if (!mSatellitePositionUpdateCallbacks.containsKey(callback)) {
throw new IllegalArgumentException(
"startSatellitePositionUpdates was never called with the callback provided.");
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 3dfc81e..b4d93fd 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -180,6 +180,19 @@
String getIsimImpi(int subId);
/**
+ * Fetches the ISIM PrivateUserIdentity (EF_IMPI) based on subId
+ *
+ * @param subId subscriptionId
+ * @return IMPI (IMS private user identity) of type string or null if EF_IMPI is not available.
+ * @throws IllegalArgumentException if the subscriptionId is not valid
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)")
+ String getImsPrivateUserIdentity(int subId, String callingPackage, String callingFeatureId);
+
+ /**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
*/
@@ -193,6 +206,14 @@
String[] getIsimImpu(int subId);
/**
+ * Fetches the ISIM public user identities (EF_IMPU) from UICC based on subId
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +
+ "anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})")
+ List<Uri> getImsPublicUserIdentities(int subId, String callingPackage,
+ String callingFeatureId);
+
+ /**
* Returns the IMS Service Table (IST) that was loaded from the ISIM.
* @return IMS Service Table or null if not present or not loaded
*/
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index f87785b..9e022f0 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -5,9 +5,7 @@
method public android.content.Context createCredentialProtectedStorageContext();
method public java.io.File getPreloadsFileCache();
method public boolean isCredentialProtectedStorage();
- method public void sendBroadcast(android.content.Intent, String, android.os.Bundle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle);
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index fed953c..67d3195 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -31,7 +31,7 @@
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
-class ImeAppAutoFocusHelper
+class ImeShownOnAppStartHelper
@JvmOverloads
constructor(
instr: Instrumentation,
@@ -121,11 +121,14 @@
}
fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
- val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
- "toggle_fixed_portrait_btn")), FIND_TIMEOUT)
+ val button =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "toggle_fixed_portrait_btn")),
+ FIND_TIMEOUT
+ )
require(button != null) {
"Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
+ "was left in an unknown state (e.g. Screen turned off)"
}
button.click()
mInstrumentation.waitForIdleSync()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 18792a8..d9aec10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -294,10 +294,10 @@
.StateSyncBuilder()
.add("pipWindowMinimized") {
val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
+ it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
val pipRegion = pipAppWindow.frameRegion
return@add windowRect.coversMoreThan(pipRegion)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
deleted file mode 100644
index 70443d8..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
+++ /dev/null
@@ -1,30 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTestCfArm(flicker: FlickerTest) :
- CloseImeAutoOpenWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
deleted file mode 100644
index 2ffc757..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
+++ /dev/null
@@ -1,30 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToHomeTestCfArm(flicker: FlickerTest) :
- CloseImeAutoOpenWindowToHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index c72405c..0f59d81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -37,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeEditorPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 840575a..b40720b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -41,7 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
index 34bd455..96b23bc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
@@ -26,4 +26,4 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTestCfArm(flicker: FlickerTest) : OpenImeWindowTest(flicker)
+class CloseImeOnGoHomeTestCfArm(flicker: FlickerTest) : CloseImeOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index 2081424..aa1e772 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -48,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
similarity index 90%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
index 0e6cb53..ed5d309 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
@@ -26,5 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTestCfArm(flicker: FlickerTest) :
- OpenImeWindowFromFixedOrientationAppTest(flicker)
+open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: FlickerTest) :
+ CloseImeShownOnAppStartOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
similarity index 91%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index 522f9e7..dfbfde8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -48,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
similarity index 88%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
index 87d9abd..0a89991 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+ CloseImeShownOnAppStartToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 13feeb1..f6838f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -43,7 +43,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
similarity index 90%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
index 87d9abd..37e8c6b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class CloseImeToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+ CloseImeToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 7d1f6cb..a744cd7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -46,7 +46,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(flicker) {
private val simpleApp = SimpleAppHelper(instrumentation)
private val testApp = ImeAppHelper(instrumentation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
index 87d9abd..116bc1b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: FlickerTest) :
+ CloseImeToHomeOnFinishActivityTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
deleted file mode 100644
index f40f5e6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
+++ /dev/null
@@ -1,29 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTestCfArm(flicker: FlickerTest) : CloseImeWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
deleted file mode 100644
index 04cae53..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
+++ /dev/null
@@ -1,30 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTestCfArm(flicker: FlickerTest) :
- LaunchAppShowImeOnStartTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
deleted file mode 100644
index c40dfae..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
+++ /dev/null
@@ -1,30 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowAndCloseTestCfArm(flicker: FlickerTest) :
- OpenImeWindowAndCloseTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 806ce99..09ef54f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -19,11 +19,11 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -34,15 +34,15 @@
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
+ * FlickerTests:OpenImeWindowToFixedPortraitAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -52,9 +52,7 @@
// Enable letterbox when the app calls setRequestedOrientation
device.executeShellCommand("cmd window set-ignore-orientation-request true")
}
- transitions {
- testApp.toggleFixPortraitOrientation(wmHelper)
- }
+ transitions { testApp.toggleFixPortraitOrientation(wmHelper) }
teardown {
testApp.exit()
device.executeShellCommand("cmd window set-ignore-orientation-request false")
@@ -64,17 +62,13 @@
@Postsubmit
@Test
fun imeLayerVisibleStart() {
- flicker.assertLayersStart {
- this.isVisible(ComponentNameMatcher.IME)
- }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Postsubmit
@Test
fun imeLayerExistsEnd() {
- flicker.assertLayersEnd {
- this.isVisible(ComponentNameMatcher.IME)
- }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
@Postsubmit
@@ -86,7 +80,7 @@
}
flicker.assertLayersEnd {
this.visibleRegion(ComponentNameMatcher.IME)
- .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
+ .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
}
}
@@ -96,7 +90,7 @@
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayersEnd {
this.visibleRegion(testApp.or(ComponentNameMatcher.LETTERBOX))
- .coversExactly(displayBounds)
+ .coversExactly(displayBounds)
}
}
@@ -111,14 +105,13 @@
@JvmStatic
fun getParams(): Collection<FlickerTest> {
return FlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(
- PlatformConsts.Rotation.ROTATION_90,
- ),
- supportedNavigationModes = listOf(
- PlatformConsts.NavBar.MODE_3BUTTON,
- PlatformConsts.NavBar.MODE_GESTURAL
- )
- )
+ supportedRotations =
+ listOf(
+ PlatformConsts.Rotation.ROTATION_90,
+ ),
+ supportedNavigationModes =
+ listOf(PlatformConsts.NavBar.MODE_3BUTTON, PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
deleted file mode 100644
index 8e6d7dc..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
+++ /dev/null
@@ -1,29 +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.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTestCfArm(flicker: FlickerTest) : OpenImeWindowToOverViewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index d41843f..89d37db 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -23,7 +23,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -45,8 +45,10 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
similarity index 86%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
index 87d9abd..307821f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 38791a2..8b25d7a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -41,8 +41,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ReOpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 477ddb3..1e908d6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -23,7 +23,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
@@ -47,9 +47,11 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
@Before
open fun before() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
similarity index 87%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
index 1d3658e..82c38a3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
@@ -28,5 +28,5 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTestCfArm(flicker: FlickerTest) :
- SwitchImeWindowsFromGestureNavTest(flicker)
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
similarity index 93%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
index e297e89..6c6003f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
@@ -38,8 +38,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(flicker: FlickerTest) :
- SwitchImeWindowsFromGestureNavTest(flicker) {
+class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 6685cf7..0fced8c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -72,8 +72,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
private val initializeApp = ImeStateInitializeHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
index 87d9abd..e7ecb87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeOnStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
similarity index 96%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 5b830e5..36747cc7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -37,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
index 87d9abd..0c53155 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: FlickerTest) :
+ ShowImeWhenFocusingOnInputFieldTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
similarity index 93%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index cb530da..7ef09c7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -25,7 +25,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -45,8 +45,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
similarity index 96%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index d95af9d..fd420454 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
@@ -46,8 +46,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
similarity index 90%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
index 87d9abd..9308fbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeWhileEnteringOverviewTestCfArm(flicker: FlickerTest) :
+ ShowImeWhileEnteringOverviewTest(flicker)
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index fdba9a6..80c7a21 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -120,6 +120,15 @@
</intent-filter>
</activity>
+ <activity android:name="ColorBitmapActivity"
+ android:label="Bitmaps/BitmapColors"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name="PathOffsetActivity"
android:label="Path/Offset"
android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
new file mode 100644
index 0000000..017de60
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -0,0 +1,196 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.HardwareBufferRenderer;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageWriter;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
+ AdapterView.OnItemSelectedListener {
+
+ private static final int WIDTH = 512;
+ private static final int HEIGHT = 512;
+
+ private ImageView mImageView;
+ private SurfaceView mSurfaceView;
+ private HardwareBuffer mGradientBuffer;
+ private ImageWriter mImageWriter;
+ private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
+ private String mCurrentColorName = "sRGB";
+
+ private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+ HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
+ RenderNode node = new RenderNode("content");
+ node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
+
+ Canvas canvas = node.beginRecording();
+ LinearGradient gradient = new LinearGradient(
+ 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
+ 0xFFFFFFFF, Shader.TileMode.CLAMP);
+ Paint paint = new Paint();
+ paint.setShader(gradient);
+ canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
+ node.endRecording();
+
+ renderer.setContentRoot(node);
+
+ ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
+ renderer.obtainRenderRequest()
+ .setColorSpace(colorSpace)
+ .draw(Executors.newSingleThreadExecutor(), result -> {
+ result.getFence().await(Duration.ofSeconds(3));
+ resolvedBuffer.run();
+ });
+ return resolvedBuffer;
+ }
+
+ private FutureTask<HardwareBuffer> getGradientBuffer() {
+ HardwareBuffer buffer = HardwareBuffer.create(
+ WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
+ return authorGradientBuffer(buffer);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+
+ mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(
+ this, android.R.layout.simple_spinner_item, mColorNames);
+
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Spinner spinner = new Spinner(this);
+ spinner.setAdapter(adapter);
+ spinner.setOnItemSelectedListener(this);
+
+ mGradientBuffer = getGradientBuffer().get();
+
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+ mImageView = new ImageView(this);
+
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.getHolder().addCallback(this);
+
+ linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+ linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+
+ setContentView(linearLayout);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private ColorSpace getFromName(String name) {
+ if (name.equals("sRGB")) {
+ return ColorSpace.get(ColorSpace.Named.SRGB);
+ } else if (name.equals("BT2020_HLG")) {
+ return ColorSpace.get(ColorSpace.Named.BT2020_HLG);
+ } else if (name.equals("BT2020_PQ")) {
+ return ColorSpace.get(ColorSpace.Named.BT2020_PQ);
+ }
+
+ throw new RuntimeException("Unrecognized Colorspace!");
+ }
+
+ private void populateBuffers() {
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+ mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+ copy.setColorSpace(mColorSpace);
+ mImageView.setImageBitmap(copy);
+
+ try (Image image = mImageWriter.dequeueInputImage()) {
+ authorGradientBuffer(image.getHardwareBuffer()).get();
+ image.setDataSpace(mColorSpace.getDataSpace());
+ mImageWriter.queueInputImage(image);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ mImageWriter = new ImageWriter.Builder(holder.getSurface())
+ .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
+ | HardwareBuffer.USAGE_COMPOSER_OVERLAY)
+ .build();
+ populateBuffers();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mImageWriter.close();
+ mImageWriter = null;
+ }
+
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mCurrentColorName = mColorNames[position];
+ mColorSpace = getFromName(mCurrentColorName);
+ populateBuffers();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+}