Merge "Fix potential deadlock in applyUserRestrictions methods." into udc-dev
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index e84aea1..b6ea54d 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -34,6 +34,55 @@
test_suites: ["device-tests"],
data: [
+ ":QueriesAll4",
+ ":QueriesAll31",
+ ":QueriesAll43",
+ ":QueriesAll15",
+ ":QueriesAll27",
+ ":QueriesAll39",
+ ":QueriesAll11",
+ ":QueriesAll23",
+ ":QueriesAll35",
+ ":QueriesAll47",
+ ":QueriesAll9",
+ ":QueriesAll19",
+ ":QueriesAll1",
+ ":QueriesAll5",
+ ":QueriesAll40",
+ ":QueriesAll20",
+ ":QueriesAll32",
+ ":QueriesAll48",
+ ":QueriesAll16",
+ ":QueriesAll28",
+ ":QueriesAll44",
+ ":QueriesAll12",
+ ":QueriesAll24",
+ ":QueriesAll36",
+ ":QueriesAll6",
+ ":QueriesAll2",
+ ":QueriesAll41",
+ ":QueriesAll21",
+ ":QueriesAll37",
+ ":QueriesAll49",
+ ":QueriesAll17",
+ ":QueriesAll29",
+ ":QueriesAll33",
+ ":QueriesAll45",
+ ":QueriesAll13",
+ ":QueriesAll25",
+ ":QueriesAll7",
+ ":QueriesAll3",
+ ":QueriesAll30",
+ ":QueriesAll42",
+ ":QueriesAll10",
+ ":QueriesAll26",
+ ":QueriesAll38",
+ ":QueriesAll18",
+ ":QueriesAll22",
+ ":QueriesAll34",
+ ":QueriesAll46",
+ ":QueriesAll14",
+ ":QueriesAll8",
":QueriesAll0",
":perfetto_artifacts",
],
diff --git a/apct-tests/perftests/settingsprovider/AndroidManifest.xml b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
index 9509c83..82f882a 100644
--- a/apct-tests/perftests/settingsprovider/AndroidManifest.xml
+++ b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
@@ -19,7 +19,8 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index c956bf5..64b2423 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -59,6 +60,19 @@
void reportAppUsage(String packageName, int userId);
/**
+ * @return {@code true} if the given notification is associated with any user-initiated jobs.
+ */
+ boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+ int userId, @NonNull String packageName);
+
+ /**
+ * @return {@code true} if the given notification channel is associated with any user-initiated
+ * jobs.
+ */
+ boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+ int userId, String packageName);
+
+ /**
* Report a snapshot of sync-related jobs back to the sync manager
*/
JobStorePersistStats getPersistStats();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 4477e94..8bd3d127 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1925,6 +1925,20 @@
return null;
}
+ @GuardedBy("mLock")
+ boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
+ String packageName) {
+ return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
+ notificationId, userId, packageName);
+ }
+
+ @GuardedBy("mLock")
+ boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+ int userId, String packageName) {
+ return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+ notificationChannel, userId, packageName);
+ }
+
@NonNull
private JobServiceContext createNewJobServiceContext() {
return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index 5a12142..f6e00ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -31,6 +31,7 @@
import android.util.SparseSetArray;
import com.android.server.LocalServices;
+import com.android.server.job.controllers.JobStatus;
import com.android.server.notification.NotificationManagerInternal;
class JobNotificationCoordinator {
@@ -52,16 +53,18 @@
@NonNull
public final UserPackage userPackage;
public final int notificationId;
+ public final String notificationChannel;
public final int appPid;
public final int appUid;
@JobService.JobEndNotificationPolicy
public final int jobEndNotificationPolicy;
NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid,
- int notificationId,
+ int notificationId, String notificationChannel,
@JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
this.userPackage = userPackage;
this.notificationId = notificationId;
+ this.notificationChannel = notificationChannel;
this.appPid = appPid;
this.appUid = appUid;
this.jobEndNotificationPolicy = jobEndNotificationPolicy;
@@ -84,14 +87,14 @@
removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
}
final int userId = UserHandle.getUserId(callingUid);
- // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
- // eg., by calling NotificationManager.cancel/All or deleting the notification channel
- mNotificationManagerInternal.enqueueNotification(
- packageName, packageName, callingUid, callingPid, /* tag */ null,
- notificationId, notification, userId);
+ final JobStatus jobStatus = hostingContext.getRunningJobLocked();
+ if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+ notification.flags |= Notification.FLAG_USER_INITIATED_JOB;
+ }
final UserPackage userPackage = UserPackage.of(userId, packageName);
final NotificationDetails details = new NotificationDetails(
- userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy);
+ userPackage, callingPid, callingUid, notificationId, notification.getChannelId(),
+ jobEndNotificationPolicy);
SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage);
if (appNotifications == null) {
appNotifications = new SparseSetArray<>();
@@ -99,6 +102,11 @@
}
appNotifications.add(notificationId, hostingContext);
mNotificationDetails.put(hostingContext, details);
+ // Call into NotificationManager after internal data structures have been updated since
+ // NotificationManager calls into this class to check for any existing associations.
+ mNotificationManagerInternal.enqueueNotification(
+ packageName, packageName, callingUid, callingPid, /* tag */ null,
+ notificationId, notification, userId);
}
void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
@@ -113,6 +121,9 @@
Slog.wtf(TAG, "Association data structures not in sync");
return;
}
+ final String packageName = details.userPackage.packageName;
+ final int userId = UserHandle.getUserId(details.appUid);
+ boolean stripUijFlag = true;
ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
if (associatedContexts == null || associatedContexts.isEmpty()) {
// No more jobs using this notification. Apply the final job stop policy.
@@ -120,12 +131,63 @@
// so the user doesn't get confused about the app state.
if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
|| stopReason == JobParameters.STOP_REASON_USER) {
- final String packageName = details.userPackage.packageName;
mNotificationManagerInternal.cancelNotification(
packageName, packageName, details.appUid, details.appPid, /* tag */ null,
- details.notificationId, UserHandle.getUserId(details.appUid));
+ details.notificationId, userId);
+ stripUijFlag = false;
+ }
+ } else {
+ // Strip the UIJ flag only if there are no other UIJs associated with the notification
+ stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs(
+ details.notificationId, userId, packageName);
+ }
+ if (stripUijFlag) {
+ // Strip the user-initiated job flag from the notification.
+ mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification(
+ packageName, details.notificationId, userId);
+ }
+ }
+
+ boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+ int userId, String packageName) {
+ final UserPackage pkgDetails = UserPackage.of(userId, packageName);
+ final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails);
+ if (associations == null) {
+ return false;
+ }
+ final ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId);
+ if (associatedContexts == null) {
+ return false;
+ }
+
+ // Check if any UIJs associated with this package are using the same notification
+ for (int i = associatedContexts.size() - 1; i >= 0; i--) {
+ final JobStatus jobStatus = associatedContexts.valueAt(i).getRunningJobLocked();
+ if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+ return true;
}
}
+ return false;
+ }
+
+ boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+ int userId, String packageName) {
+ for (int i = mNotificationDetails.size() - 1; i >= 0; i--) {
+ final JobServiceContext jsc = mNotificationDetails.keyAt(i);
+ final NotificationDetails details = mNotificationDetails.get(jsc);
+ // Check if the details for the given notification match and if the associated job
+ // was started as a user initiated job
+ if (details != null
+ && UserHandle.getUserId(details.appUid) == userId
+ && details.userPackage.packageName.equals(packageName)
+ && details.notificationChannel.equals(notificationChannel)) {
+ final JobStatus jobStatus = jsc.getRunningJobLocked();
+ if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+ return true;
+ }
+ }
+ }
+ return false;
}
private void validateNotification(@NonNull String packageName, int callingUid,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 887ee5f..aef9dd0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -33,6 +33,7 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.UidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.IUserVisibleJobObserver;
@@ -1250,7 +1251,7 @@
return pkg;
}
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ final private IUidObserver mUidObserver = new UidObserver() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
final SomeArgs args = SomeArgs.obtain();
@@ -1264,19 +1265,13 @@
mHandler.obtainMessage(MSG_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
}
- @Override public void onUidActive(int uid) throws RemoteException {
+ @Override public void onUidActive(int uid) {
mHandler.obtainMessage(MSG_UID_ACTIVE, uid, 0).sendToTarget();
}
@Override public void onUidIdle(int uid, boolean disabled) {
mHandler.obtainMessage(MSG_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
}
-
- @Override public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override public void onUidProcAdjChanged(int uid) {
- }
};
public Context getTestableContext() {
@@ -3717,6 +3712,30 @@
}
@Override
+ public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+ int userId, String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs(
+ notificationId, userId, packageName);
+ }
+ }
+
+ @Override
+ public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+ String notificationChannel, int userId, String packageName) {
+ if (packageName == null || notificationChannel == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+ notificationChannel, userId, packageName);
+ }
+ }
+
+ @Override
public JobStorePersistStats getPersistStats() {
synchronized (mLock) {
return new JobStorePersistStats(mJobs.getPersistStats());
diff --git a/boot/OWNERS b/boot/OWNERS
index 0e258d0..3fe1a41 100644
--- a/boot/OWNERS
+++ b/boot/OWNERS
@@ -2,5 +2,5 @@
file:platform/build/soong:/OWNERS
# art-team@ manages the boot image profiles for frameworks
-per-file boot-* = calin@google.com, yawanng@google.com, ngeoffray@google.com
-per-file preloaded-classes* = calin@google.com, yawanng@google.com, ngeoffray@google.com
+per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
diff --git a/config/OWNERS b/config/OWNERS
index 74813bc..6a5df76 100644
--- a/config/OWNERS
+++ b/config/OWNERS
@@ -1,8 +1,8 @@
include /ZYGOTE_OWNERS
# art-team@ manages the boot image profiles
-per-file boot-* = ngeoffray@google.com, vmarko@google.com
-per-file dirty-image-objects = ngeoffray@google.com, vmarko@google.com
-per-file generate-preloaded-classes.sh = ngeoffray@google.com, vmarko@google.com
-per-file preloaded-classes* = ngeoffray@google.com, vmarko@google.com
+per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file dirty-image-objects = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com
+per-file generate-preloaded-classes.sh = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 873234a..ffbfe82 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -338,10 +338,12 @@
}
public class Notification implements android.os.Parcelable {
+ method public boolean isUserInitiatedJob();
method public boolean shouldShowForegroundImmediately();
field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
+ field public static final int FLAG_USER_INITIATED_JOB = 32768; // 0x8000
}
public final class NotificationChannel implements android.os.Parcelable {
@@ -351,10 +353,10 @@
method public void setDeleted(boolean);
method public void setDeletedTimeMs(long);
method public void setDemoted(boolean);
- method public void setFgServiceShown(boolean);
method public void setImportanceLockedByCriticalDeviceFunction(boolean);
method public void setImportantConversation(boolean);
method public void setOriginalImportance(int);
+ method public void setUserVisibleTaskShown(boolean);
}
public final class NotificationChannelGroup implements android.os.Parcelable {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3615435..08a1af4 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -2878,9 +2878,7 @@
public IAccessibilityServiceClientWrapper(Context context, Looper looper,
Callbacks callback) {
- mCallback = callback;
- mContext = context;
- mExecutor = new HandlerExecutor(new Handler(looper));
+ this(context, new HandlerExecutor(new Handler(looper)), callback);
}
public void init(IAccessibilityServiceConnection connection, int connectionId,
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 6dcf331..808f25e 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -860,10 +860,10 @@
* <p>
* <strong>Generated by the system.</strong>
* </p>
- * @return The id.
+ * @return The id (or {@code null} if the component is not set yet).
*/
public String getId() {
- return mComponentName.flattenToShortString();
+ return mComponentName == null ? null : mComponentName.flattenToShortString();
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7b4aeec..29e135f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -41,6 +41,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
@@ -2078,6 +2079,9 @@
case BadForegroundServiceNotificationException.TYPE_ID:
throw new BadForegroundServiceNotificationException(message);
+ case BadUserInitiatedJobNotificationException.TYPE_ID:
+ throw new BadUserInitiatedJobNotificationException(message);
+
case MissingRequestPasswordComplexityPermissionException.TYPE_ID:
throw new MissingRequestPasswordComplexityPermissionException(message);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index f4373a6..ee24263 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -243,4 +243,11 @@
* @hide
*/
boolean lockScreenWallpaperExists();
+
+ /**
+ * Temporary method for project b/197814683.
+ * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
+ * @hide
+ */
+ boolean isLockscreenLiveWallpaperEnabled();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7aedd30..f803739 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -722,6 +722,16 @@
*/
public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
+ /**
+ * Bit to be bitwise-ored into the {@link #flags} field that should be
+ * set if this notification represents a currently running user-initiated job.
+ *
+ * This flag is for internal use only; applications cannot set this flag directly.
+ * @hide
+ */
+ @TestApi
+ public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
+
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -731,7 +741,8 @@
@IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
- FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
+ FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE,
+ FLAG_USER_INITIATED_JOB})
@Retention(RetentionPolicy.SOURCE)
public @interface NotificationFlags{};
@@ -4067,8 +4078,9 @@
* notification if alerts for this notification's group should be handled by a different
* notification. This is only applicable for notifications that belong to a
* {@link #setGroup(String) group}. This must be called on all notifications you want to
- * mute. For example, if you want only the summary of your group to make noise, all
- * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}.
+ * mute. For example, if you want only the summary of your group to make noise and/or peek
+ * on screen, all children in the group should have the group alert behavior
+ * {@link #GROUP_ALERT_SUMMARY}.
*
* <p> The default value is {@link #GROUP_ALERT_ALL}.</p>
*/
@@ -5726,7 +5738,8 @@
}
private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
- boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
+ boolean hideSnoozeButton = mN.isFgsOrUij()
+ || mN.fullScreenIntent != null
|| isBackgroundColorized(p)
|| p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
@@ -6867,6 +6880,24 @@
}
/**
+ * @return whether this notification is associated with a user initiated job
+ * @hide
+ */
+ @TestApi
+ public boolean isUserInitiatedJob() {
+ return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0;
+ }
+
+ /**
+ * @return whether this notification is associated with either a foreground service or
+ * a user initiated job
+ * @hide
+ */
+ public boolean isFgsOrUij() {
+ return isForegroundService() || isUserInitiatedJob();
+ }
+
+ /**
* Describe whether this notification's content such that it should always display
* immediately when tied to a foreground service, even if the system might generally
* avoid showing the notifications for short-lived foreground service lifetimes.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 9615b68..746dcb6 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -32,7 +32,6 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -150,6 +149,10 @@
private static final String ATT_CONTENT_TYPE = "content_type";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_USER_LOCKED = "locked";
+ /**
+ * This attribute represents both foreground services and user initiated jobs in U+.
+ * It was not renamed in U on purpose, in order to avoid creating an unnecessary migration path.
+ */
private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
private static final String ATT_GROUP = "group";
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
@@ -249,7 +252,7 @@
// Bitwise representation of fields that have been changed by the user, preventing the app from
// making changes to these fields.
private int mUserLockedFields;
- private boolean mFgServiceShown;
+ private boolean mUserVisibleTaskShown;
private boolean mVibrationEnabled;
private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
@@ -317,7 +320,7 @@
mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
}
mUserLockedFields = in.readInt();
- mFgServiceShown = in.readByte() != 0;
+ mUserVisibleTaskShown = in.readByte() != 0;
mVibrationEnabled = in.readByte() != 0;
mShowBadge = in.readByte() != 0;
mDeleted = in.readByte() != 0;
@@ -371,7 +374,7 @@
dest.writeByte(mLights ? (byte) 1 : (byte) 0);
dest.writeLongArray(mVibration);
dest.writeInt(mUserLockedFields);
- dest.writeByte(mFgServiceShown ? (byte) 1 : (byte) 0);
+ dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0);
dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
@@ -418,8 +421,8 @@
* @hide
*/
@TestApi
- public void setFgServiceShown(boolean shown) {
- mFgServiceShown = shown;
+ public void setUserVisibleTaskShown(boolean shown) {
+ mUserVisibleTaskShown = shown;
}
/**
@@ -845,8 +848,8 @@
/**
* @hide
*/
- public boolean isFgServiceShown() {
- return mFgServiceShown;
+ public boolean isUserVisibleTaskShown() {
+ return mUserVisibleTaskShown;
}
/**
@@ -965,7 +968,7 @@
parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS));
setGroup(parser.getAttributeValue(null, ATT_GROUP));
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
- setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
+ setUserVisibleTaskShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
@@ -1072,8 +1075,8 @@
if (getUserLockedFields() != 0) {
out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
}
- if (isFgServiceShown()) {
- out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isFgServiceShown());
+ if (isUserVisibleTaskShown()) {
+ out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isUserVisibleTaskShown());
}
if (canShowBadge()) {
out.attributeBoolean(null, ATT_SHOW_BADGE, canShowBadge());
@@ -1147,7 +1150,7 @@
record.put(ATT_LIGHT_COLOR, Integer.toString(getLightColor()));
record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
- record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isFgServiceShown()));
+ record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown()));
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
@@ -1239,7 +1242,7 @@
&& mLights == that.mLights
&& getLightColor() == that.getLightColor()
&& getUserLockedFields() == that.getUserLockedFields()
- && isFgServiceShown() == that.isFgServiceShown()
+ && isUserVisibleTaskShown() == that.isUserVisibleTaskShown()
&& mVibrationEnabled == that.mVibrationEnabled
&& mShowBadge == that.mShowBadge
&& isDeleted() == that.isDeleted()
@@ -1265,8 +1268,8 @@
public int hashCode() {
int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
getLockscreenVisibility(), getSound(), mLights, getLightColor(),
- getUserLockedFields(),
- isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
+ getUserLockedFields(), isUserVisibleTaskShown(),
+ mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
mImportanceLockedDefaultApp, mOriginalImportance,
mParentId, mConversationId, mDemoted, mImportantConvo);
@@ -1304,7 +1307,7 @@
+ ", mLightColor=" + mLightColor
+ ", mVibration=" + Arrays.toString(mVibration)
+ ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
- + ", mFgServiceShown=" + mFgServiceShown
+ + ", mUserVisibleTaskShown=" + mUserVisibleTaskShown
+ ", mVibrationEnabled=" + mVibrationEnabled
+ ", mShowBadge=" + mShowBadge
+ ", mDeleted=" + mDeleted
@@ -1342,7 +1345,7 @@
}
}
proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
- proto.write(NotificationChannelProto.FG_SERVICE_SHOWN, mFgServiceShown);
+ proto.write(NotificationChannelProto.USER_VISIBLE_TASK_SHOWN, mUserVisibleTaskShown);
proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d2f2c3c..80f64e0 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -726,8 +726,9 @@
* Cancels a previously posted notification.
*
* <p>If the notification does not currently represent a
- * {@link Service#startForeground(int, Notification) foreground service}, it will be
- * removed from the UI and live
+ * {@link Service#startForeground(int, Notification) foreground service} or a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+ * it will be removed from the UI and live
* {@link android.service.notification.NotificationListenerService notification listeners}
* will be informed so they can remove the notification from their UIs.</p>
*/
@@ -740,8 +741,9 @@
* Cancels a previously posted notification.
*
* <p>If the notification does not currently represent a
- * {@link Service#startForeground(int, Notification) foreground service}, it will be
- * removed from the UI and live
+ * {@link Service#startForeground(int, Notification) foreground service} or a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+ * it will be removed from the UI and live
* {@link android.service.notification.NotificationListenerService notification listeners}
* will be informed so they can remove the notification from their UIs.</p>
*/
@@ -754,8 +756,9 @@
* Cancels a previously posted notification.
*
* <p>If the notification does not currently represent a
- * {@link Service#startForeground(int, Notification) foreground service}, it will be
- * removed from the UI and live
+ * {@link Service#startForeground(int, Notification) foreground service} or a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+ * it will be removed from the UI and live
* {@link android.service.notification.NotificationListenerService notification listeners}
* will be informed so they can remove the notification from their UIs.</p>
*
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index 620adbe..c5ad110 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -102,6 +102,21 @@
}
/**
+ * Exception used to crash an app process when the system finds an error in a user-initiated job
+ * notification.
+ *
+ * @hide
+ */
+ public static class BadUserInitiatedJobNotificationException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 6;
+
+ public BadUserInitiatedJobNotificationException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
* Exception used to crash an app process when it calls a setting activity that requires
* the {@code REQUEST_PASSWORD_COMPLEXITY} permission.
*
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 86f6a93..658e084 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -46,6 +46,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
@@ -111,6 +112,7 @@
private static final String LOG_TAG = UiAutomation.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = false;
private static final int CONNECTION_ID_UNDEFINED = -1;
@@ -321,11 +323,18 @@
* @hide
*/
public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "connectWithTimeout: user=" + Process.myUserHandle().getIdentifier()
+ + ", flags=" + DebugUtils.flagsToString(UiAutomation.class, "FLAG_", flags)
+ + ", timeout=" + timeoutMillis + "ms");
+ }
synchronized (mLock) {
throwIfConnectedLocked();
if (mConnectionState == ConnectionState.CONNECTING) {
+ if (DEBUG) Log.d(LOG_TAG, "already connecting");
return;
}
+ if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTING");
mConnectionState = ConnectionState.CONNECTING;
mRemoteCallbackThread = new HandlerThread("UiAutomation");
mRemoteCallbackThread.start();
@@ -341,6 +350,7 @@
// If UiAutomation is not allowed to use the accessibility subsystem, the
// connection state should keep disconnected and not to start the client connection.
if (!useAccessibility()) {
+ if (DEBUG) Log.d(LOG_TAG, "setting state to DISCONNECTED");
mConnectionState = ConnectionState.DISCONNECTED;
return;
}
@@ -357,6 +367,7 @@
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
+ if (DEBUG) Log.d(LOG_TAG, "setting state to FAILED");
mConnectionState = ConnectionState.FAILED;
throw new TimeoutException("Timeout while connecting " + this);
}
@@ -1367,7 +1378,8 @@
UserHandle userHandle) {
try {
if (DEBUG) {
- Log.i(LOG_TAG, "Granting runtime permission");
+ Log.i(LOG_TAG, "Granting runtime permission (" + permission + ") to package "
+ + packageName + " on user " + userHandle);
}
// Calling out without a lock held.
mUiAutomationConnection.grantRuntimePermission(packageName,
@@ -1592,7 +1604,7 @@
private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
- super(null, looper, new Callbacks() {
+ super(/* context= */ null, looper, new Callbacks() {
private final int mGenerationId = generationId;
/**
@@ -1606,10 +1618,21 @@
@Override
public void init(int connectionId, IBinder windowToken) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
+ + windowToken + ", user=" + Process.myUserHandle()
+ + ", mGenerationId=" + mGenerationId
+ + ", UiAutomation.mGenerationId="
+ + UiAutomation.this.mGenerationId);
+ }
synchronized (mLock) {
if (isGenerationChangedLocked()) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "init(): returning because generation id changed");
+ }
return;
}
+ if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTED");
mConnectionState = ConnectionState.CONNECTED;
mConnectionId = connectionId;
mLock.notifyAll();
@@ -1662,9 +1685,20 @@
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (VERBOSE) {
+ Log.v(LOG_TAG, "onAccessibilityEvent(" + Process.myUserHandle() + "): "
+ + event);
+ }
+
final OnAccessibilityEventListener listener;
synchronized (mLock) {
if (isGenerationChangedLocked()) {
+ if (VERBOSE) {
+ Log.v(LOG_TAG, "onAccessibilityEvent(): returning because "
+ + "generation id changed (from "
+ + UiAutomation.this.mGenerationId + " to "
+ + mGenerationId + ")");
+ }
return;
}
// It is not guaranteed that the accessibility framework sends events by the
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 3a32f23..13e800e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -103,6 +103,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public UiAutomationConnection() {
+ Log.d(TAG, "Created on user " + Process.myUserHandle());
}
@Override
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 4d55fee..ebd525e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -142,9 +142,6 @@
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
- /** Temporary feature flag for project b/197814683 */
- private final boolean mLockscreenLiveWallpaper;
-
/** {@hide} */
private static final String PROP_WALLPAPER = "ro.config.wallpaper";
/** {@hide} */
@@ -306,6 +303,7 @@
private final Context mContext;
private final boolean mWcgEnabled;
private final ColorManagementProxy mCmProxy;
+ private Boolean mIsLockscreenLiveWallpaperEnabled = null;
/**
* Special drawable that draws a wallpaper as fast as possible. Assumes
@@ -794,8 +792,6 @@
mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
&& context.getResources().getBoolean(R.bool.config_enableWcgMode);
mCmProxy = new ColorManagementProxy(context);
- mLockscreenLiveWallpaper = context.getResources()
- .getBoolean(R.bool.config_independentLockscreenLiveWallpaper);
}
// no-op constructor called just by DisabledWallpaperManager
@@ -803,7 +799,6 @@
mContext = null;
mCmProxy = null;
mWcgEnabled = false;
- mLockscreenLiveWallpaper = false;
}
/**
@@ -827,7 +822,19 @@
*/
@TestApi
public boolean isLockscreenLiveWallpaperEnabled() {
- return mLockscreenLiveWallpaper;
+ if (sGlobals == null) {
+ mIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
+ "persist.wm.debug.lockscreen_live_wallpaper", false);
+ }
+ if (mIsLockscreenLiveWallpaperEnabled == null) {
+ try {
+ mIsLockscreenLiveWallpaperEnabled =
+ sGlobals.mService.isLockscreenLiveWallpaperEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mIsLockscreenLiveWallpaperEnabled;
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bad6c77..4d3338b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6413,7 +6413,7 @@
public void lockNow(@LockNowFlag int flags) {
if (mService != null) {
try {
- mService.lockNow(flags, mParentInstance);
+ mService.lockNow(flags, mContext.getPackageName(), mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9634,7 +9634,8 @@
* @see #isProfileOwnerApp
* @see #isDeviceOwnerApp
* @param admin Which {@link DeviceAdminReceiver} this request is associate with.
- * @param profileName The name of the profile.
+ * @param profileName The name of the profile. If the name is longer than 200 characters
+ * it will be truncated.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void setProfileName(@NonNull ComponentName admin, String profileName) {
@@ -13646,8 +13647,8 @@
* privacy-sensitive events happening outside the managed profile would have been redacted
* already.
*
- * @param admin Which device admin this request is associated with. Null if the caller is not
- * a device admin
+ * @param admin Which device admin this request is associated with, or {@code null}
+ * if called by a delegated app.
* @param enabled whether security logging should be enabled or not.
* @throws SecurityException if the caller is not permitted to control security logging.
* @see #setAffiliationIds
@@ -13699,8 +13700,8 @@
* it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
* See {@link #isAffiliatedUser}.
*
- * @param admin Which device admin this request is associated with. Null if the caller is not
- * a device admin.
+ * @param admin Which device admin this request is associated with, or {@code null}
+ * if called by a delegated app.
* @return the new batch of security logs which is a list of {@link SecurityEvent},
* or {@code null} if rate limitation is exceeded or if logging is currently disabled.
* @throws SecurityException if the caller is not allowed to access security logging,
@@ -13857,8 +13858,8 @@
* it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
* See {@link #isAffiliatedUser}.
*
- * @param admin Which device admin this request is associated with. Null if the caller is not
- * a device admin.
+ * @param admin Which device admin this request is associated with, or {@code null}
+ * if called by a delegated app.
* @return Device logs from before the latest reboot of the system, or {@code null} if this API
* is not supported on the device.
* @throws SecurityException if the caller is not allowed to access security logging, or
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index a498913..052f670 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1802,14 +1802,6 @@
PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE";
/**
- * Text for dialog shown when user tries to open a work app when the work profile is
- * turned off, confirming that the user wants to turn on access to their
- * work apps.
- */
- public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE =
- PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE";
-
- /**
* Notification title shown when work profile is credential encrypted and requires
* the user to unlock before it's usable.
*/
@@ -1857,6 +1849,17 @@
public static final String WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON =
PREFIX + "TURN_ON_WORK_PROFILE_BUTTON_TEXT";
+ public static final String MINIRESOLVER_OPEN_IN_WORK =
+ PREFIX + "MINIRESOLVER_OPEN_IN_WORK";
+
+ public static final String MINIRESOLVER_OPEN_IN_PERSONAL =
+ PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
+
+ public static final String MINIRESOLVER_USE_WORK_BROWSER =
+ PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
+
+ public static final String MINIRESOLVER_USE_PERSONAL_BROWSER =
+ PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8d508c0..9b0b18a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -119,7 +119,7 @@
void setRequiredStrongAuthTimeout(in ComponentName who, String callerPackageName, long timeMs, boolean parent);
long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent);
- void lockNow(int flags, boolean parent);
+ void lockNow(int flags, String callerPackageName, boolean parent);
/**
* @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index 30aad96..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -28,7 +28,9 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -45,6 +47,10 @@
*/
@SystemApi
public final class IntentFilterPolicyKey extends PolicyKey {
+
+ private static final String TAG = "IntentFilterPolicyKey";
+
+ private static final String TAG_INTENT_FILTER_ENTRY = "filter";
private final IntentFilter mFilter;
/**
@@ -83,7 +89,9 @@
@Override
public void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+ serializer.startTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
mFilter.writeToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
}
/**
@@ -93,11 +101,27 @@
public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER);
- IntentFilter filter = new IntentFilter();
- filter.readFromXml(parser);
+ IntentFilter filter = readIntentFilterFromXml(parser);
return new IntentFilterPolicyKey(identifier, filter);
}
+ @Nullable
+ private IntentFilter readIntentFilterFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ if (tag.equals(TAG_INTENT_FILTER_ENTRY)) {
+ IntentFilter filter = new IntentFilter();
+ filter.readFromXml(parser);
+ return filter;
+ }
+ Log.e(TAG, "Unknown tag: " + tag);
+ }
+ Log.e(TAG, "Error parsing IntentFilterPolicyKey, IntentFilter not found");
+ return null;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index a3874f7..8132b81 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -186,16 +186,6 @@
mAppWidgetProviderInfo = appWidgetProviderInfo;
mSliceUri = sliceUri;
mExtras = extras != null ? extras : new Bundle();
-
- int published = 0;
- if (mSearchAction != null) published++;
- if (mShortcutInfo != null) published++;
- if (mAppWidgetProviderInfo != null) published++;
- if (mSliceUri != null) published++;
- if (published > 1) {
- throw new IllegalStateException("Only one of SearchAction, ShortcutInfo,"
- + " AppWidgetProviderInfo, SliceUri can be assigned in a SearchTarget.");
- }
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index da6784b..2ca2b79bc 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -64,6 +64,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -171,6 +172,7 @@
public VirtualDevice createVirtualDevice(
int associationId,
@NonNull VirtualDeviceParams params) {
+ Objects.requireNonNull(params, "params must not be null");
try {
return new VirtualDevice(mService, mContext, associationId, params);
} catch (RemoteException e) {
@@ -409,6 +411,9 @@
@NonNull PendingIntent pendingIntent,
@NonNull Executor executor,
@NonNull IntConsumer listener) {
+ Objects.requireNonNull(pendingIntent, "pendingIntent must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(listener, "listener must not be null");
mVirtualDeviceInternal.launchPendingIntent(
displayId, pendingIntent, executor, listener);
}
@@ -483,6 +488,7 @@
@NonNull VirtualDisplayConfig config,
@Nullable @CallbackExecutor Executor executor,
@Nullable VirtualDisplay.Callback callback) {
+ Objects.requireNonNull(config, "config must not be null");
return mVirtualDeviceInternal.createVirtualDisplay(config, executor, callback);
}
@@ -503,6 +509,7 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
+ Objects.requireNonNull(config, "config must not be null");
return mVirtualDeviceInternal.createVirtualDpad(config);
}
@@ -514,6 +521,7 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
+ Objects.requireNonNull(config, "config must not be null");
return mVirtualDeviceInternal.createVirtualKeyboard(config);
}
@@ -550,6 +558,7 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
+ Objects.requireNonNull(config, "config must not be null");
return mVirtualDeviceInternal.createVirtualMouse(config);
}
@@ -587,6 +596,7 @@
@NonNull
public VirtualTouchscreen createVirtualTouchscreen(
@NonNull VirtualTouchscreenConfig config) {
+ Objects.requireNonNull(config, "config must not be null");
return mVirtualDeviceInternal.createVirtualTouchscreen(config);
}
@@ -659,6 +669,7 @@
@NonNull VirtualDisplay display,
@Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
+ Objects.requireNonNull(display, "display must not be null");
return mVirtualDeviceInternal.createVirtualAudioDevice(display, executor, callback);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 58b0571..154068e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4233,6 +4233,13 @@
"com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
/**
+ * Activity Action: Shows the contrast setting dialog.
+ * @hide
+ */
+ public static final String ACTION_SHOW_CONTRAST_DIALOG =
+ "com.android.intent.action.SHOW_CONTRAST_DIALOG";
+
+ /**
* Broadcast Action: A global button was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
* caused the broadcast.
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 96a42e2..563ed7d 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -38,6 +38,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.ParcelFileDescriptor;
+import android.window.IDumpCallback;
import com.android.internal.infra.AndroidFuture;
@@ -116,4 +117,10 @@
String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
int userId);
Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage, int userId);
+
+ /** Register a callback to be called right before the wmtrace data is moved to the bugreport. */
+ void registerDumpCallback(IDumpCallback cb);
+
+ /** Unregister a callback, so that it won't be called when LauncherApps dumps. */
+ void unRegisterDumpCallback(IDumpCallback cb);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8989006..27270d9 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -17,6 +17,7 @@
package android.content.pm;
import static android.Manifest.permission;
+import static android.Manifest.permission.READ_FRAME_BUFFER;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -68,6 +69,7 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
+import android.window.IDumpCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
@@ -1172,6 +1174,32 @@
}
/**
+ * Register a callback to be called right before the wmtrace data is moved to the bugreport.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void registerDumpCallback(IDumpCallback cb) {
+ try {
+ mService.registerDumpCallback(cb);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregister a callback, so that it won't be called when LauncherApps dumps.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void unRegisterDumpCallback(IDumpCallback cb) {
+ try {
+ mService.unRegisterDumpCallback(cb);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Returns {@link ShortcutInfo}s that match {@code query}.
*
* <p>Callers must be allowed to access the shortcut information, as defined in {@link
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index cb988df..30fd77c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2991,6 +2991,10 @@
* The update ownership enforcement can only be enabled on initial installation. Set
* this to {@code true} on package update is a no-op.
*
+ * Apps may opt themselves out of update ownership by setting the
+ * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a>
+ * attribute in their manifest to <code>false</code>.
+ *
* Note: To enable the update ownership enforcement, the installer must have the
* {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP}
* permission.
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 269bec2..408f7ed 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -138,6 +138,11 @@
*/
private final boolean mIsSdkLibrary;
+ /**
+ * Indicates if this package allows an installer to declare update ownership of it.
+ */
+ private final boolean mAllowUpdateOwnership;
+
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
int versionCodeMajor, int revisionCode, int installLocation,
@@ -148,7 +153,7 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean allowUpdateOwnership) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -182,6 +187,7 @@
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
+ mAllowUpdateOwnership = allowUpdateOwnership;
}
/**
@@ -474,6 +480,9 @@
return mRollbackDataPolicy;
}
+ /**
+ * Indicates if this app contains a {@link android.app.admin.DeviceAdminReceiver}.
+ */
@DataClass.Generated.Member
public boolean isHasDeviceAdminReceiver() {
return mHasDeviceAdminReceiver;
@@ -487,11 +496,19 @@
return mIsSdkLibrary;
}
+ /**
+ * Indicates if this package allows an installer to declare update ownership of it.
+ */
+ @DataClass.Generated.Member
+ public boolean isAllowUpdateOwnership() {
+ return mAllowUpdateOwnership;
+ }
+
@DataClass.Generated(
- time = 1643063342990L,
+ time = 1680122754650L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 64fed63..a4339d4 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -127,7 +127,8 @@
null /* isFeatureSplits */, null /* usesSplitNames */,
null /* configForSplit */, null /* splitApkPaths */,
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
- null /* requiredSplitTypes */, null /* splitTypes */));
+ null /* requiredSplitTypes */, null, /* splitTypes */
+ baseApk.isAllowUpdateOwnership()));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -273,7 +274,8 @@
return input.success(
new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
- baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes));
+ baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes,
+ baseApk.isAllowUpdateOwnership()));
}
/**
@@ -400,6 +402,8 @@
"isFeatureSplit", false);
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isSplitRequired", false);
+ boolean allowUpdateOwnership = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "allowUpdateOwnership", true);
String configForSplit = parser.getAttributeValue(null, "configForSplit");
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
@@ -583,7 +587,7 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary));
+ hasDeviceAdminReceiver, isSdkLibrary, allowUpdateOwnership));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index e2789c9..e24b932 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -110,10 +110,16 @@
*/
private final boolean mIsSdkLibrary;
+ /**
+ * Indicates if this package allows an installer to declare update ownership of it.
+ */
+ private final boolean mAllowUpdateOwnership;
+
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes,
- int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes) {
+ int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes,
+ boolean allowUpdateOwnership) {
// The following paths may be different from the path in ApkLite because we
// move or rename the APK files. Use parameters to indicate the correct paths.
mPath = path;
@@ -144,6 +150,7 @@
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
+ mAllowUpdateOwnership = allowUpdateOwnership;
}
/**
@@ -414,12 +421,19 @@
return mIsSdkLibrary;
}
+ /**
+ * Indicates if this package allows an installer to declare update ownership of it.
+ */
+ @DataClass.Generated.Member
+ public boolean isAllowUpdateOwnership() {
+ return mAllowUpdateOwnership;
+ }
+
@DataClass.Generated(
- time = 1643132127068L,
+ time = 1680125514341L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures =
- "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 00ce17a..9140d02 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -460,9 +460,17 @@
return false;
}
+ /**
+ * Returns whether the service is enabled.
+ *
+ * @hide
+ */
private boolean isServiceEnabled() {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
+ try {
+ return mService.isServiceEnabled();
+ } catch (RemoteException e) {
+ return false;
+ }
}
/**
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 5fde96b..b779c56 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -58,5 +58,7 @@
List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter);
List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter);
+
+ boolean isServiceEnabled();
}
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index bf3df90..6ac6581 100644
--- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -23,11 +23,6 @@
* @hide
*/
oneway interface IBiometricContextListener {
- // Called when aod or awake (screen on) status changes.
- // These may be called while the device is still transitioning to the new state
- // (i.e. about to become awake or enter doze)
- void onDozeChanged(boolean isAod, boolean isAwake);
-
@VintfStability
@Backing(type="int")
enum FoldState {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b5281a5..2aead3c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1758,10 +1758,12 @@
/**
* Key for the brightness throttling data as a String formatted:
* <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
- * Where the latter part is repeated for each throttling level, and the entirety is repeated
- * for each display, separated by a semicolon.
+ * [,<throttlingId>]?
+ * Where [<severity as string>,<brightness cap>] is repeated for each throttling level.
+ * The entirety is repeated for each display and throttling id, separated by a semicolon.
* For example:
* 123,1,critical,0.8;456,2,moderate,0.9,critical,0.7
+ * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7
*/
String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
}
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 70bd504..15ed450 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -26,13 +26,17 @@
import android.content.Context;
import android.os.IBinder;
import android.util.Slog;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.android.internal.policy.PhoneWindow;
+import java.util.Objects;
+
/**
* Window of type {@code LayoutParams.TYPE_INPUT_METHOD_DIALOG} for drawing
* Handwriting Ink on screen.
@@ -185,4 +189,12 @@
return getDecorView().getVisibility() == View.VISIBLE
&& mInkView != null && mInkView.isVisibleToUser();
}
+
+ void dispatchHandwritingEvent(@NonNull MotionEvent event) {
+ final View decor = getDecorView();
+ Objects.requireNonNull(decor);
+ final ViewRootImpl viewRoot = decor.getViewRootImpl();
+ Objects.requireNonNull(viewRoot);
+ viewRoot.enqueueInputEvent(event);
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ee9d8a4..a9c4818 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2563,7 +2563,7 @@
*/
public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
if (mInkWindow != null && mInkWindow.isInkViewVisible()) {
- mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
+ mInkWindow.dispatchHandwritingEvent(motionEvent);
} else {
if (mPendingEvents == null) {
mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
@@ -2576,7 +2576,7 @@
if (mInkWindow == null) {
break;
}
- mInkWindow.getDecorView().dispatchTouchEvent(event);
+ mInkWindow.dispatchHandwritingEvent(event);
}
mPendingEvents.clear();
}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b977606..e87333f 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -231,12 +231,18 @@
}
private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
- public ICancellationSignal onBeginGetCredential(BeginGetCredentialRequest request,
+ @Override
+ public void onBeginGetCredential(BeginGetCredentialRequest request,
IBeginGetCredentialCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
+ try {
+ callback.onCancellable(transport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onBeginGetCredential,
@@ -267,7 +273,6 @@
}
}
));
- return transport;
}
private void enforceRemoteEntryPermission() {
String permission =
@@ -280,12 +285,17 @@
}
@Override
- public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+ public void onBeginCreateCredential(BeginCreateCredentialRequest request,
IBeginCreateCredentialCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
+ try {
+ callback.onCancellable(transport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onBeginCreateCredential,
@@ -316,16 +326,20 @@
}
}
));
- return transport;
}
@Override
- public ICancellationSignal onClearCredentialState(ClearCredentialStateRequest request,
+ public void onClearCredentialState(ClearCredentialStateRequest request,
IClearCredentialStateCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
+ try {
+ callback.onCancellable(transport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onClearCredentialState,
@@ -350,7 +364,6 @@
}
}
));
- return transport;
}
};
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
index ab855ef..4b73cbc 100644
--- a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -1,6 +1,7 @@
package android.service.credentials;
import android.service.credentials.BeginCreateCredentialResponse;
+import android.os.ICancellationSignal;
/**
* Interface from the system to a credential provider service.
@@ -10,4 +11,5 @@
oneway interface IBeginCreateCredentialCallback {
void onSuccess(in BeginCreateCredentialResponse request);
void onFailure(String errorType, in CharSequence message);
+ void onCancellable(in ICancellationSignal cancellation);
}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
index 73e9870..0710fb3 100644
--- a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
+++ b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
@@ -1,6 +1,8 @@
package android.service.credentials;
import android.service.credentials.BeginGetCredentialResponse;
+import android.os.ICancellationSignal;
+
/**
* Interface from the system to a credential provider service.
@@ -10,4 +12,5 @@
oneway interface IBeginGetCredentialCallback {
void onSuccess(in BeginGetCredentialResponse response);
void onFailure(String errorType, in CharSequence message);
+ void onCancellable(in ICancellationSignal cancellation);
}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
index ec805d0..5751318 100644
--- a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
+++ b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
@@ -16,12 +16,16 @@
package android.service.credentials;
+import android.os.ICancellationSignal;
+
+
/**
* Callback for onClearCredentialState request.
*
* @hide
*/
-interface IClearCredentialStateCallback {
- oneway void onSuccess();
- oneway void onFailure(String errorType, CharSequence message);
+oneway interface IClearCredentialStateCallback {
+ void onSuccess();
+ void onFailure(String errorType, CharSequence message);
+ void onCancellable(in ICancellationSignal cancellation);
}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index 626dd78..ebb4a4e 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -30,8 +30,8 @@
*
* @hide
*/
-interface ICredentialProviderService {
- ICancellationSignal onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback);
- ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
- ICancellationSignal onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback);
+oneway interface ICredentialProviderService {
+ void onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback);
+ void onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
+ void onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback);
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 5c2b389..402da28 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1008,9 +1008,8 @@
.allowAlarms(allowAlarms)
.allowMedia(allowMedia)
.allowSystem(allowSystem)
- .allowConversations(allowConversations
- ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom)
- : ZenPolicy.PEOPLE_TYPE_NONE);
+ .allowConversations(allowConversations ? allowConversationsFrom
+ : ZenPolicy.CONVERSATION_SENDERS_NONE);
if (suppressedVisualEffects == 0) {
builder.showAllVisualEffects();
} else {
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index d48d566..e6dad27 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -24,12 +24,12 @@
import android.content.Context;
import android.icu.text.DateFormatSymbols;
import android.icu.text.DateTimePatternGenerator;
+import android.icu.util.ULocale;
import android.os.Build;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
-import android.text.TextUtils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -265,11 +265,13 @@
* @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
*/
public static String getBestDateTimePattern(Locale locale, String skeleton) {
- DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
+ ULocale uLocale = ULocale.forLocale(locale);
+ DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
boolean allowDuplicateFields = !CompatChanges.isChangeEnabled(
DISALLOW_DUPLICATE_FIELD_IN_SKELETON);
- return dtpg.getBestPattern(skeleton, DateTimePatternGenerator.MATCH_NO_OPTIONS,
+ String pattern = dtpg.getBestPattern(skeleton, DateTimePatternGenerator.MATCH_NO_OPTIONS,
allowDuplicateFields);
+ return getCompatibleEnglishPattern(uLocale, pattern);
}
/**
@@ -303,10 +305,11 @@
*/
@UnsupportedAppUsage
public static String getTimeFormatString(Context context, int userHandle) {
- DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
- context.getResources().getConfiguration().locale);
- return is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm")
+ ULocale uLocale = ULocale.forLocale(context.getResources().getConfiguration().locale);
+ DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
+ String pattern = is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm")
: dtpg.getBestPattern("hm");
+ return getCompatibleEnglishPattern(uLocale, pattern);
}
/**
@@ -713,4 +716,21 @@
public static DateFormatSymbols getIcuDateFormatSymbols(Locale locale) {
return new DateFormatSymbols(android.icu.util.GregorianCalendar.class, locale);
}
+
+ /**
+ * See http://b/266731719. It mirrors the implementation in
+ * {@link libcore.icu.SimpleDateFormatData.DateTimeFormatStringGenerator#postProcessPattern}
+ */
+ private static String getCompatibleEnglishPattern(ULocale locale, String pattern) {
+ if (pattern == null || locale == null || !"en".equals(locale.getLanguage())) {
+ return pattern;
+ }
+
+ String region = locale.getCountry();
+ if (region != null && !region.isEmpty() && !"US".equals(region)) {
+ return pattern;
+ }
+
+ return pattern.replace('\u202f', ' ');
+ }
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index eb467e0..992a586 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -75,6 +75,11 @@
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
/**
+ * The maximum number of signers supported by the v2 APK signature scheme.
+ */
+ private static final int MAX_V2_SIGNERS = 10;
+
+ /**
* Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
*
* <p><b>NOTE: This method does not verify the signature.</b>
@@ -183,6 +188,11 @@
}
while (signers.hasRemaining()) {
signerCount++;
+ if (signerCount > MAX_V2_SIGNERS) {
+ throw new SecurityException(
+ "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS
+ + " signers");
+ }
try {
ByteBuffer signer = getLengthPrefixedSlice(signers);
X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index 4525490..a6aca33 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -78,6 +78,11 @@
"SHA1",
};
+ /**
+ * The maximum number of signers supported by the JAR signature scheme.
+ */
+ private static final int MAX_JAR_SIGNERS = 10;
+
private final String jarName;
private final StrictJarManifest manifest;
private final HashMap<String, byte[]> metaEntries;
@@ -293,10 +298,16 @@
return false;
}
+ int signerCount = 0;
Iterator<String> it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+ if (++signerCount > MAX_JAR_SIGNERS) {
+ throw new SecurityException(
+ "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS
+ + " signers");
+ }
verifyCertificate(key);
it.remove();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 152fa08..2f5cd54 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -265,6 +265,7 @@
private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV;
private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
+ private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
private static final int LOGTAG_INPUT_FOCUS = 62001;
@@ -7122,7 +7123,8 @@
mJoystick.cancel();
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
- mTouchNavigation.cancel(event);
+ // Touch navigation events cannot be cancelled since they are dispatched
+ // immediately.
}
}
}
@@ -7641,392 +7643,109 @@
}
/**
- * Creates dpad events from unhandled touch navigation movements.
+ * Creates DPAD events from unhandled touch navigation movements.
*/
final class SyntheticTouchNavigationHandler extends Handler {
private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
- private static final boolean LOCAL_DEBUG = false;
- // Assumed nominal width and height in millimeters of a touch navigation pad,
- // if no resolution information is available from the input system.
- private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
- private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
-
- /* TODO: These constants should eventually be moved to ViewConfiguration. */
-
- // The nominal distance traveled to move by one unit.
- private static final int TICK_DISTANCE_MILLIMETERS = 12;
-
- // Minimum and maximum fling velocity in ticks per second.
- // The minimum velocity should be set such that we perform enough ticks per
- // second that the fling appears to be fluid. For example, if we set the minimum
- // to 2 ticks per second, then there may be up to half a second delay between the next
- // to last and last ticks which is noticeably discrete and jerky. This value should
- // probably not be set to anything less than about 4.
- // If fling accuracy is a problem then consider tuning the tick distance instead.
- private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
- private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
-
- // Fling velocity decay factor applied after each new key is emitted.
- // This parameter controls the deceleration and overall duration of the fling.
- // The fling stops automatically when its velocity drops below the minimum
- // fling velocity defined above.
- private static final float FLING_TICK_DECAY = 0.8f;
-
- /* The input device that we are tracking. */
-
+ // The id of the input device that is being tracked.
private int mCurrentDeviceId = -1;
private int mCurrentSource;
- private boolean mCurrentDeviceSupported;
- /* Configuration for the current input device. */
-
- // The scaled tick distance. A movement of this amount should generally translate
- // into a single dpad event in a given direction.
- private float mConfigTickDistance;
-
- // The minimum and maximum scaled fling velocity.
- private float mConfigMinFlingVelocity;
- private float mConfigMaxFlingVelocity;
-
- /* Tracking state. */
-
- // The velocity tracker for detecting flings.
- private VelocityTracker mVelocityTracker;
-
- // The active pointer id, or -1 if none.
- private int mActivePointerId = -1;
-
- // Location where tracking started.
- private float mStartX;
- private float mStartY;
-
- // Most recently observed position.
- private float mLastX;
- private float mLastY;
-
- // Accumulated movement delta since the last direction key was sent.
- private float mAccumulatedX;
- private float mAccumulatedY;
-
- // Set to true if any movement was delivered to the app.
- // Implies that tap slop was exceeded.
- private boolean mConsumedMovement;
-
- // The most recently sent key down event.
- // The keycode remains set until the direction changes or a fling ends
- // so that repeated key events may be generated as required.
- private long mPendingKeyDownTime;
- private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
- private int mPendingKeyRepeatCount;
private int mPendingKeyMetaState;
- // The current fling velocity while a fling is in progress.
- private boolean mFlinging;
- private float mFlingVelocity;
+ private final GestureDetector mGestureDetector = new GestureDetector(mContext,
+ new GestureDetector.OnGestureListener() {
+ @Override
+ public boolean onDown(@NonNull MotionEvent e) {
+ // This can be ignored since it's not clear what KeyEvent this will
+ // belong to.
+ return true;
+ }
- public SyntheticTouchNavigationHandler() {
+ @Override
+ public void onShowPress(@NonNull MotionEvent e) {
+
+ }
+
+ @Override
+ public boolean onSingleTapUp(@NonNull MotionEvent e) {
+ dispatchTap(e.getEventTime());
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+ float distanceX, float distanceY) {
+ // Scroll doesn't translate to DPAD events so should be ignored.
+ return true;
+ }
+
+ @Override
+ public void onLongPress(@NonNull MotionEvent e) {
+ // Long presses don't translate to DPAD events so should be ignored.
+ }
+
+ @Override
+ public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+ float velocityX, float velocityY) {
+ dispatchFling(velocityX, velocityY, e2.getEventTime());
+ return true;
+ }
+ });
+
+ SyntheticTouchNavigationHandler() {
super(true);
}
public void process(MotionEvent event) {
+ if (event.getDevice() == null) {
+ // The current device is not supported.
+ if (DEBUG_TOUCH_NAVIGATION) {
+ Log.d(LOCAL_TAG,
+ "Current device not supported so motion event is not processed");
+ }
+ return;
+ }
+ mPendingKeyMetaState = event.getMetaState();
// Update the current device information.
- final long time = event.getEventTime();
final int deviceId = event.getDeviceId();
final int source = event.getSource();
if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
- finishKeys(time);
- finishTracking(time);
mCurrentDeviceId = deviceId;
mCurrentSource = source;
- mCurrentDeviceSupported = false;
- InputDevice device = event.getDevice();
- if (device != null) {
- // In order to support an input device, we must know certain
- // characteristics about it, such as its size and resolution.
- InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
- InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
- if (xRange != null && yRange != null) {
- mCurrentDeviceSupported = true;
-
- // Infer the resolution if it not actually known.
- float xRes = xRange.getResolution();
- if (xRes <= 0) {
- xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
- }
- float yRes = yRange.getResolution();
- if (yRes <= 0) {
- yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
- }
- float nominalRes = (xRes + yRes) * 0.5f;
-
- // Precompute all of the configuration thresholds we will need.
- mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
- mConfigMinFlingVelocity =
- MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
- mConfigMaxFlingVelocity =
- MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
-
- if (LOCAL_DEBUG) {
- Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
- + " (" + Integer.toHexString(mCurrentSource) + "): "
- + ", mConfigTickDistance=" + mConfigTickDistance
- + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
- + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
- }
- }
- }
- }
- if (!mCurrentDeviceSupported) {
- return;
}
- // Handle the event.
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- boolean caughtFling = mFlinging;
- finishKeys(time);
- finishTracking(time);
- mActivePointerId = event.getPointerId(0);
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(event);
- mStartX = event.getX();
- mStartY = event.getY();
- mLastX = mStartX;
- mLastY = mStartY;
- mAccumulatedX = 0;
- mAccumulatedY = 0;
-
- // If we caught a fling, then pretend that the tap slop has already
- // been exceeded to suppress taps whose only purpose is to stop the fling.
- mConsumedMovement = caughtFling;
- break;
- }
-
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP: {
- if (mActivePointerId < 0) {
- break;
- }
- final int index = event.findPointerIndex(mActivePointerId);
- if (index < 0) {
- finishKeys(time);
- finishTracking(time);
- break;
- }
-
- mVelocityTracker.addMovement(event);
- final float x = event.getX(index);
- final float y = event.getY(index);
- mAccumulatedX += x - mLastX;
- mAccumulatedY += y - mLastY;
- mLastX = x;
- mLastY = y;
-
- // Consume any accumulated movement so far.
- final int metaState = event.getMetaState();
- consumeAccumulatedMovement(time, metaState);
-
- // Detect taps and flings.
- if (action == MotionEvent.ACTION_UP) {
- if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
- // It might be a fling.
- mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
- final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
- final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
- if (!startFling(time, vx, vy)) {
- finishKeys(time);
- }
- }
- finishTracking(time);
- }
- break;
- }
-
- case MotionEvent.ACTION_CANCEL: {
- finishKeys(time);
- finishTracking(time);
- break;
- }
- }
+ // Interpret the event.
+ mGestureDetector.onTouchEvent(event);
}
- public void cancel(MotionEvent event) {
- if (mCurrentDeviceId == event.getDeviceId()
- && mCurrentSource == event.getSource()) {
- final long time = event.getEventTime();
- finishKeys(time);
- finishTracking(time);
- }
+ private void dispatchTap(long time) {
+ dispatchEvent(time, KeyEvent.KEYCODE_DPAD_CENTER);
}
- private void finishKeys(long time) {
- cancelFling();
- sendKeyUp(time);
- }
-
- private void finishTracking(long time) {
- if (mActivePointerId >= 0) {
- mActivePointerId = -1;
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- private void consumeAccumulatedMovement(long time, int metaState) {
- final float absX = Math.abs(mAccumulatedX);
- final float absY = Math.abs(mAccumulatedY);
- if (absX >= absY) {
- if (absX >= mConfigTickDistance) {
- mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
- KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
- mAccumulatedY = 0;
- mConsumedMovement = true;
- }
+ private void dispatchFling(float x, float y, long time) {
+ if (Math.abs(x) > Math.abs(y)) {
+ dispatchEvent(time,
+ x > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT);
} else {
- if (absY >= mConfigTickDistance) {
- mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
- KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
- mAccumulatedX = 0;
- mConsumedMovement = true;
- }
+ dispatchEvent(time, y > 0 ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP);
}
}
- private float consumeAccumulatedMovement(long time, int metaState,
- float accumulator, int negativeKeyCode, int positiveKeyCode) {
- while (accumulator <= -mConfigTickDistance) {
- sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
- accumulator += mConfigTickDistance;
+ private void dispatchEvent(long time, int keyCode) {
+ if (DEBUG_TOUCH_NAVIGATION) {
+ Log.d(LOCAL_TAG, "Dispatching DPAD events DOWN and UP with keycode " + keyCode);
}
- while (accumulator >= mConfigTickDistance) {
- sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
- accumulator -= mConfigTickDistance;
- }
- return accumulator;
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, mPendingKeyMetaState,
+ mCurrentDeviceId, /* scancode= */ 0, KeyEvent.FLAG_FALLBACK,
+ mCurrentSource));
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_UP, keyCode, /* repeat= */ 0, mPendingKeyMetaState,
+ mCurrentDeviceId, /* scancode= */ 0, KeyEvent.FLAG_FALLBACK,
+ mCurrentSource));
}
-
- private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
- if (mPendingKeyCode != keyCode) {
- sendKeyUp(time);
- mPendingKeyDownTime = time;
- mPendingKeyCode = keyCode;
- mPendingKeyRepeatCount = 0;
- } else {
- mPendingKeyRepeatCount += 1;
- }
- mPendingKeyMetaState = metaState;
-
- // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
- // but it doesn't quite make sense when simulating the events in this way.
- if (LOCAL_DEBUG) {
- Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
- + ", repeatCount=" + mPendingKeyRepeatCount
- + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
- }
- enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
- KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
- mPendingKeyMetaState, mCurrentDeviceId,
- KeyEvent.FLAG_FALLBACK, mCurrentSource));
- }
-
- private void sendKeyUp(long time) {
- if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
- if (LOCAL_DEBUG) {
- Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
- + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
- }
- enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
- KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
- mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
- mCurrentSource));
- mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
- }
- }
-
- private boolean startFling(long time, float vx, float vy) {
- if (LOCAL_DEBUG) {
- Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
- + ", min=" + mConfigMinFlingVelocity);
- }
-
- // Flings must be oriented in the same direction as the preceding movements.
- switch (mPendingKeyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (-vx >= mConfigMinFlingVelocity
- && Math.abs(vy) < mConfigMinFlingVelocity) {
- mFlingVelocity = -vx;
- break;
- }
- return false;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (vx >= mConfigMinFlingVelocity
- && Math.abs(vy) < mConfigMinFlingVelocity) {
- mFlingVelocity = vx;
- break;
- }
- return false;
-
- case KeyEvent.KEYCODE_DPAD_UP:
- if (-vy >= mConfigMinFlingVelocity
- && Math.abs(vx) < mConfigMinFlingVelocity) {
- mFlingVelocity = -vy;
- break;
- }
- return false;
-
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (vy >= mConfigMinFlingVelocity
- && Math.abs(vx) < mConfigMinFlingVelocity) {
- mFlingVelocity = vy;
- break;
- }
- return false;
- }
-
- // Post the first fling event.
- mFlinging = postFling(time);
- return mFlinging;
- }
-
- private boolean postFling(long time) {
- // The idea here is to estimate the time when the pointer would have
- // traveled one tick distance unit given the current fling velocity.
- // This effect creates continuity of motion.
- if (mFlingVelocity >= mConfigMinFlingVelocity) {
- long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
- postAtTime(mFlingRunnable, time + delay);
- if (LOCAL_DEBUG) {
- Log.d(LOCAL_TAG, "Posted fling: velocity="
- + mFlingVelocity + ", delay=" + delay
- + ", keyCode=" + mPendingKeyCode);
- }
- return true;
- }
- return false;
- }
-
- private void cancelFling() {
- if (mFlinging) {
- removeCallbacks(mFlingRunnable);
- mFlinging = false;
- }
- }
-
- private final Runnable mFlingRunnable = new Runnable() {
- @Override
- public void run() {
- final long time = SystemClock.uptimeMillis();
- sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
- mFlingVelocity *= FLING_TICK_DECAY;
- if (!postFling(time)) {
- mFlinging = false;
- finishKeys(time);
- }
- }
- };
}
final class SyntheticKeyboardHandler {
@@ -9272,7 +8991,7 @@
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- void enqueueInputEvent(InputEvent event) {
+ public void enqueueInputEvent(InputEvent event) {
enqueueInputEvent(event, null, 0, false);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cda1f3a..02b3478 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -495,64 +495,64 @@
* Transition flag: Keyguard is going away, but keeping the notification shade open
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1;
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = (1 << 0); // 0x1
/**
* Transition flag: Keyguard is going away, but doesn't want an animation for it
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2;
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = (1 << 1); // 0x2
/**
* Transition flag: Keyguard is going away while it was showing the system wallpaper.
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4;
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = (1 << 2); // 0x4
/**
* Transition flag: Keyguard is going away with subtle animation.
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION = 0x8;
-
- /**
- * Transition flag: Keyguard is going away to the launcher, and it needs us to clear the task
- * snapshot of the launcher because it has changed something in the Launcher window.
- * @hide
- */
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT = 0x16;
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION = (1 << 3); // 0x8
/**
* Transition flag: App is crashed.
* @hide
*/
- int TRANSIT_FLAG_APP_CRASHED = 0x10;
+ int TRANSIT_FLAG_APP_CRASHED = (1 << 4); // 0x10
/**
* Transition flag: A window in a new task is being opened behind an existing one in another
* activity's task.
* @hide
*/
- int TRANSIT_FLAG_OPEN_BEHIND = 0x20;
+ int TRANSIT_FLAG_OPEN_BEHIND = (1 << 5); // 0x20
/**
* Transition flag: The keyguard is locked throughout the whole transition.
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40;
+ int TRANSIT_FLAG_KEYGUARD_LOCKED = (1 << 6); // 0x40
/**
* Transition flag: Indicates that this transition is for recents animation.
* TODO(b/188669821): Remove once special-case logic moves to shell.
* @hide
*/
- int TRANSIT_FLAG_IS_RECENTS = 0x80;
+ int TRANSIT_FLAG_IS_RECENTS = (1 << 7); // 0x80
/**
* Transition flag: Indicates that keyguard should go away with this transition.
* @hide
*/
- int TRANSIT_FLAG_KEYGUARD_GOING_AWAY = 0x100;
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY = (1 << 8); // 0x100
+
+ /**
+ * Transition flag: Keyguard is going away to the launcher, and it needs us to clear the task
+ * snapshot of the launcher because it has changed something in the Launcher window.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT = (1 << 9); // 0x200
/**
* @hide
@@ -562,12 +562,12 @@
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION,
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER,
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
- TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT,
TRANSIT_FLAG_APP_CRASHED,
TRANSIT_FLAG_OPEN_BEHIND,
TRANSIT_FLAG_KEYGUARD_LOCKED,
TRANSIT_FLAG_IS_RECENTS,
- TRANSIT_FLAG_KEYGUARD_GOING_AWAY
+ TRANSIT_FLAG_KEYGUARD_GOING_AWAY,
+ TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
@@ -869,6 +869,42 @@
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-out from the compatibility
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
+ * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
+ * by the landscape natural orientation of the device.
+ *
+ * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation}
+ * call from an app if both of the following conditions are true:
+ * <ul>
+ * <li>Activity has requested orientation more than 2 times within 1-second timer
+ * <li>Activity is not letterboxed for fixed orientation
+ * </ul>
+ *
+ * <p>Setting this property to {@code false} informs the system that the app must be
+ * opted-out from the compatibility 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_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/274924641): Make this public API.
+ String PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
+ "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
+
+ /**
* 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.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 38b564a..d69c781 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -976,6 +976,7 @@
append(builder, "AddedCount", mAddedCount);
append(builder, "RemovedCount", mRemovedCount);
append(builder, "ParcelableData", mParcelableData);
+ append(builder, "DisplayId", mSourceDisplayId);
builder.append(" ]");
return builder;
}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 4aa612c..951eecc 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -194,6 +194,14 @@
"should_enable_autofill_on_all_view_types";
/**
+ * Whether to enable multi-line filter when checking if view is autofillable
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_MULTILINE_FILTER_ENABLED =
+ "multiline_filter_enabled";
+
+ /**
* Whether include all autofill type not none views in assist structure
*
* @hide
@@ -439,6 +447,17 @@
}
+ /**
+ * Whether should enable multi-line filter
+ *
+ * @hide
+ */
+ public static boolean shouldEnableMultilineFilter() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_MULTILINE_FILTER_ENABLED, false);
+ }
+
// START AUTOFILL PCC CLASSIFICATION FUNCTIONS
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 801b13a..f7b7d33 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -707,6 +707,9 @@
// An allowed activity set read from device config
private Set<String> mAllowedActivitySet = new ArraySet<>();
+ // Whether to enable multi-line check when checking whether view is autofillable
+ private boolean mShouldEnableMultilineFilter;
+
// Indicate whether should include all view with autofill type not none in assist structure
private boolean mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure;
@@ -889,6 +892,9 @@
mNonAutofillableImeActionIdSet =
AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
+ mShouldEnableMultilineFilter =
+ AutofillFeatureFlags.shouldEnableMultilineFilter();
+
final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag();
@@ -948,9 +954,8 @@
/**
* Whether view passes the imeAction check
*
- * @hide
*/
- public boolean isPassingImeActionCheck(EditText editText) {
+ private boolean isPassingImeActionCheck(EditText editText) {
final int actionId = editText.getImeOptions();
if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) {
Log.d(TAG, "view not autofillable - not passing ime action check");
@@ -959,6 +964,21 @@
return true;
}
+ /**
+ * Checks whether the view passed in is not multiline text
+ *
+ * @param editText the view that passed to this check
+ * @return true if the view input is not multiline, false otherwise
+ */
+ private boolean isPassingMultilineCheck(EditText editText) {
+ // check if min line is set to be greater than 1
+ if (editText.getMinLines() > 1) {
+ Log.d(TAG, "view not autofillable - has multiline input type");
+ return false;
+ }
+ return true;
+ }
+
private boolean isPackageFullyAllowedOrDeniedForAutofill(
@NonNull String listString, @NonNull String packageName) {
// If "PackageName:;" is in the string, then it the package is fully denied or allowed for
@@ -1103,6 +1123,9 @@
}
if (view instanceof EditText) {
+ if (mShouldEnableMultilineFilter && !isPassingMultilineCheck((EditText) view)) {
+ return false;
+ }
return isPassingImeActionCheck((EditText) view);
}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index ec50c69..aa9225b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -418,12 +418,15 @@
});
}
+ @Dispatching(cancellable = false)
@Override
public void cancelCancellationSignal(IBinder token) {
if (mBeamer == null) {
return;
}
- mBeamer.cancel(token);
+ dispatch(() -> {
+ mBeamer.cancel(token);
+ });
}
@Override
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 3df09c2..1f0e95e 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -49,15 +49,18 @@
import java.util.Locale;
/**
- * This widget display an analogic clock with two hands for hours and
- * minutes.
+ * This widget displays an analogic clock with two hands for hours and minutes.
*
* @attr ref android.R.styleable#AnalogClock_dial
* @attr ref android.R.styleable#AnalogClock_hand_hour
* @attr ref android.R.styleable#AnalogClock_hand_minute
* @attr ref android.R.styleable#AnalogClock_hand_second
* @attr ref android.R.styleable#AnalogClock_timeZone
- * @deprecated This widget is no longer supported.
+ * @deprecated This widget is no longer supported; except for
+ * {@link android.widget.RemoteViews} use cases like
+ * <a href="https://developer.android.com/develop/ui/views/appwidgets/overview">
+ * app widgets</a>.
+ *
*/
@RemoteView
@Deprecated
diff --git a/core/java/android/window/IDumpCallback.aidl b/core/java/android/window/IDumpCallback.aidl
new file mode 100644
index 0000000..4c825d4
--- /dev/null
+++ b/core/java/android/window/IDumpCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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 android.window;
+
+/**
+ * Callback for processes which need to feed data to another process when it dumps.
+ * @hide
+ */
+ interface IDumpCallback {
+ oneway void onDump(in ParcelFileDescriptor outFd);
+ }
\ No newline at end of file
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index d8e64d4..95451a9 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -309,7 +309,7 @@
/** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */
public void release() {
- if (mExcludeLayers.length == 0) {
+ if (mExcludeLayers == null || mExcludeLayers.length == 0) {
return;
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 75e797b..44d517a 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_IN_WORK;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -212,9 +213,7 @@
buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
((TextView) findViewById(R.id.open_cross_profile)).setText(
- getResources().getString(
- R.string.miniresolver_open_in_work,
- target.loadLabel(packageManagerForTargetUser)));
+ getOpenInWorkMessage(target.loadLabel(packageManagerForTargetUser)));
// The mini-resolver's negative button is reused in this flow to cancel the intent
((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -226,6 +225,13 @@
});
}
+ private String getOpenInWorkMessage(CharSequence targetLabel) {
+ return getSystemService(DevicePolicyManager.class).getResources().getString(
+ MINIRESOLVER_OPEN_IN_WORK,
+ () -> getString(R.string.miniresolver_open_in_work, targetLabel),
+ targetLabel);
+ }
+
private String getForwardToPersonalMessage() {
return getSystemService(DevicePolicyManager.class).getResources().getString(
FORWARD_INTENT_TO_PERSONAL,
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 73c5207..499d38c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -19,6 +19,10 @@
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_IN_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_IN_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_USE_PERSONAL_BROWSER;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_USE_WORK_BROWSER;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
@@ -46,6 +50,7 @@
import android.app.VoiceInteractor.Prompt;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -1713,14 +1718,29 @@
}
}.execute();
- ((TextView) findViewById(R.id.open_cross_profile)).setText(
- getResources().getString(
- inWorkProfile ? R.string.miniresolver_open_in_personal
- : R.string.miniresolver_open_in_work,
- otherProfileResolveInfo.getDisplayLabel()));
- ((Button) findViewById(R.id.use_same_profile_browser)).setText(
- inWorkProfile ? R.string.miniresolver_use_work_browser
- : R.string.miniresolver_use_personal_browser);
+ CharSequence targetDisplayLabel = otherProfileResolveInfo.getDisplayLabel();
+
+ DevicePolicyResourcesManager devicePolicyResourcesManager = getSystemService(
+ DevicePolicyManager.class).getResources();
+
+ if (inWorkProfile) {
+ ((TextView) findViewById(R.id.open_cross_profile)).setText(
+ devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK,
+ () -> getString(R.string.miniresolver_open_in_work, targetDisplayLabel),
+ targetDisplayLabel));
+ ((Button) findViewById(R.id.use_same_profile_browser)).setText(
+ devicePolicyResourcesManager.getString(MINIRESOLVER_USE_WORK_BROWSER,
+ () -> getString(R.string.miniresolver_use_work_browser)));
+ } else {
+ ((TextView) findViewById(R.id.open_cross_profile)).setText(
+ devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL,
+ () -> getString(R.string.miniresolver_open_in_personal,
+ targetDisplayLabel),
+ targetDisplayLabel));
+ ((Button) findViewById(R.id.use_same_profile_browser)).setText(
+ devicePolicyResourcesManager.getString(MINIRESOLVER_USE_PERSONAL_BROWSER,
+ () -> getString(R.string.miniresolver_use_personal_browser)));
+ }
findViewById(R.id.use_same_profile_browser).setOnClickListener(
v -> {
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index e47c335..73914a2 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -16,7 +16,6 @@
package com.android.internal.app;
-import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -87,17 +86,13 @@
mTelecomManager.getDefaultDialerPackage(UserHandle.of(mUserId))));
final AlertDialog.Builder builder;
- final String dialogMessage;
if (showEmergencyCallButton) {
builder = new AlertDialog.Builder(this, R.style.AlertDialogWithEmergencyButton);
- dialogMessage = getDialogMessage(R.string.work_mode_dialer_off_message);
builder.setNeutralButton(R.string.work_mode_emergency_call_button, this);
} else {
builder = new AlertDialog.Builder(this);
- dialogMessage = getDialogMessage(R.string.work_mode_off_message);
}
builder.setTitle(getDialogTitle())
- .setMessage(dialogMessage)
.setOnDismissListener(this)
.setPositiveButton(R.string.work_mode_turn_on, this)
.setNegativeButton(R.string.cancel, null);
@@ -120,12 +115,6 @@
UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title));
}
- private String getDialogMessage(int dialogMessageString) {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE,
- () -> getString(dialogMessageString));
- }
-
@Override
public void onDismiss(DialogInterface dialog) {
finish();
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 8e7fe18..098bce1 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -198,6 +198,11 @@
@Override
public int getMeasuredType() {
+ if (mDrawable == null) {
+ Log.e(TAG, "getMeasuredType() after recycle()!");
+ return MEASURED_NORMAL;
+ }
+
int measuredHeight = getMeasuredHeight();
int minImageHeight;
if (mIsIsolated) {
diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto
index c835b90..d79de5c 100644
--- a/core/proto/android/app/notification_channel.proto
+++ b/core/proto/android/app/notification_channel.proto
@@ -56,7 +56,9 @@
optional android.media.AudioAttributesProto audio_attributes = 16;
// If this is a blockable system notification channel.
optional bool is_blockable_system = 17;
- optional bool fg_service_shown = 18;
+ // On U+, this field will be true if either a foreground service or a user initiated job is
+ // shown whereas on T-, this field will only be true if a foreground service is shown.
+ optional bool user_visible_task_shown = 18;
// Default is true.
// Allows the notifications to appear outside of the shade in floating windows
optional bool allow_app_overlay = 19;
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index 9e53a91..25985eb 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -23,7 +23,7 @@
option java_multiple_files = true;
/* Represents a file full of transition entries.
- Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such
+ Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (TRNTRACE), such
that it can be easily identified. */
message TransitionTraceProto {
diff --git a/core/res/res/drawable-hdpi/pointer_copy.png b/core/res/res/drawable-hdpi/pointer_copy.png
index c5eda2e..5d06a8e 100644
--- a/core/res/res/drawable-hdpi/pointer_copy.png
+++ b/core/res/res/drawable-hdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grab.png b/core/res/res/drawable-hdpi/pointer_grab.png
index 26da04d..b76ec16 100644
--- a/core/res/res/drawable-hdpi/pointer_grab.png
+++ b/core/res/res/drawable-hdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grabbing.png b/core/res/res/drawable-hdpi/pointer_grabbing.png
index f4031a9..10013e9 100644
--- a/core/res/res/drawable-hdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-hdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_hand.png b/core/res/res/drawable-hdpi/pointer_hand.png
index a7ae55f..8a72774 100644
--- a/core/res/res/drawable-hdpi/pointer_hand.png
+++ b/core/res/res/drawable-hdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_nodrop.png b/core/res/res/drawable-hdpi/pointer_nodrop.png
index 7043323..9df140c 100644
--- a/core/res/res/drawable-hdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-hdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png
index e731108..7189dad 100644
--- a/core/res/res/drawable-mdpi/pointer_copy.png
+++ b/core/res/res/drawable-mdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy_large.png b/core/res/res/drawable-mdpi/pointer_copy_large.png
index 15ccb04..a3d487d 100644
--- a/core/res/res/drawable-mdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-mdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png
index d625b55..977b36c 100644
--- a/core/res/res/drawable-mdpi/pointer_grab.png
+++ b/core/res/res/drawable-mdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab_large.png b/core/res/res/drawable-mdpi/pointer_grab_large.png
index 9d36df0..80587ce 100644
--- a/core/res/res/drawable-mdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png
index 71bb17b..2bdcbdc 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing_large.png b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
index 5574b07..a8a599c 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png
index d7f7bed..e94b927 100644
--- a/core/res/res/drawable-mdpi/pointer_hand.png
+++ b/core/res/res/drawable-mdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand_large.png b/core/res/res/drawable-mdpi/pointer_hand_large.png
index f775464..7d89067 100644
--- a/core/res/res/drawable-mdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-mdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png
index 931b740..15764fa 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop_large.png b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
index 88f77d3..46ff5f7 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png
index 5b6cc5b..8d889e1 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy_large.png b/core/res/res/drawable-xhdpi/pointer_copy_large.png
index d78a410..860c854 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png
index 46dd3ee..dd3c6de 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab_large.png b/core/res/res/drawable-xhdpi/pointer_grab_large.png
index 1c7e63e..bcae2c9 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png
index 2fb8a9c..500fa73 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
index 3467a03..c9c6f18 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png
index 926310c..e931afe 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand_large.png b/core/res/res/drawable-xhdpi/pointer_hand_large.png
index 546b222..39ae1b7 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png
index fdfc267..4c9fe63 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
index 2b5e8a4..ae60e77 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_copy.png b/core/res/res/drawable-xxhdpi/pointer_copy.png
index bef4fb4..24661a6 100644
--- a/core/res/res/drawable-xxhdpi/pointer_copy.png
+++ b/core/res/res/drawable-xxhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grab.png b/core/res/res/drawable-xxhdpi/pointer_grab.png
index 6caa1ba..1917722 100644
--- a/core/res/res/drawable-xxhdpi/pointer_grab.png
+++ b/core/res/res/drawable-xxhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grabbing.png b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
index b52f751..a4cd9e5 100644
--- a/core/res/res/drawable-xxhdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_hand.png b/core/res/res/drawable-xxhdpi/pointer_hand.png
index f3ee077..917529b 100644
--- a/core/res/res/drawable-xxhdpi/pointer_hand.png
+++ b/core/res/res/drawable-xxhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_nodrop.png b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
index ef54301..35b4d13 100644
--- a/core/res/res/drawable-xxhdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml
index da32939..7e7c0ca 100644
--- a/core/res/res/drawable/pointer_copy_icon.xml
+++ b/core/res/res/drawable/pointer_copy_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_copy"
- android:hotSpotX="7.5dp"
+ android:hotSpotX="8.5dp"
android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_large_icon.xml b/core/res/res/drawable/pointer_copy_large_icon.xml
index 55d47b4..54e9523 100644
--- a/core/res/res/drawable/pointer_copy_large_icon.xml
+++ b/core/res/res/drawable/pointer_copy_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_copy_large"
- android:hotSpotX="22.5dp"
- android:hotSpotY="22.5dp" />
+ android:hotSpotX="21.25dp"
+ android:hotSpotY="18.75dp" />
diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml
index b3d4e78..dd1216a 100644
--- a/core/res/res/drawable/pointer_grab_icon.xml
+++ b/core/res/res/drawable/pointer_grab_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grab"
- android:hotSpotX="13.5dp"
- android:hotSpotY="13.5dp" />
+ android:hotSpotX="9.5dp"
+ android:hotSpotY="4.5dp" />
diff --git a/core/res/res/drawable/pointer_grab_large_icon.xml b/core/res/res/drawable/pointer_grab_large_icon.xml
index 343c7d2..b5dbd11 100644
--- a/core/res/res/drawable/pointer_grab_large_icon.xml
+++ b/core/res/res/drawable/pointer_grab_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grab_large"
- android:hotSpotX="33.75dp"
- android:hotSpotY="33.75dp" />
+ android:hotSpotX="23.75dp"
+ android:hotSpotY="11.25dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_large_icon.xml b/core/res/res/drawable/pointer_grabbing_large_icon.xml
index ac16265..b041c83 100644
--- a/core/res/res/drawable/pointer_grabbing_large_icon.xml
+++ b/core/res/res/drawable/pointer_grabbing_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grabbing_large"
- android:hotSpotX="25.5dp"
- android:hotSpotY="22.5dp" />
+ android:hotSpotX="21.25dp"
+ android:hotSpotY="18.75dp" />
diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml
index 3f9d1a6..0feccd2 100644
--- a/core/res/res/drawable/pointer_hand_icon.xml
+++ b/core/res/res/drawable/pointer_hand_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_hand"
android:hotSpotX="9.5dp"
- android:hotSpotY="1.5dp" />
+ android:hotSpotY="2.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_large_icon.xml b/core/res/res/drawable/pointer_hand_large_icon.xml
index cd49762..6257b0b 100644
--- a/core/res/res/drawable/pointer_hand_large_icon.xml
+++ b/core/res/res/drawable/pointer_hand_large_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_hand_large"
android:hotSpotX="23.75dp"
- android:hotSpotY="3.75dp" />
+ android:hotSpotY="6.25dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml
index 4dffd23..fb78d74 100644
--- a/core/res/res/drawable/pointer_nodrop_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_nodrop"
- android:hotSpotX="7.5dp"
+ android:hotSpotX="8.5dp"
android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_large_icon.xml b/core/res/res/drawable/pointer_nodrop_large_icon.xml
index 602c744..2385e11 100644
--- a/core/res/res/drawable/pointer_nodrop_large_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_nodrop_large"
- android:hotSpotX="22.5dp"
- android:hotSpotY="22.5dp" />
+ android:hotSpotX="21.25dp"
+ android:hotSpotY="18.75dp" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ef19fc1..3ff6351 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -680,6 +680,20 @@
rotation. -->
<bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
+ <!-- Indicates whether the window manager pauses autorotation when folding or unfolding
+ a foldable device based on hinge angle sensor events and physical display switch events. -->
+ <bool name="config_windowManagerPauseRotationWhenUnfolding">false</bool>
+
+ <!-- Amount of time during which autorotation will be disabled since last hinge angle event -->
+ <integer name="config_pauseRotationWhenUnfolding_maxHingeAngle">0</integer>
+
+ <!-- Maximum hinge angle event to be considered to disable autorotation when folding or
+ unfolding -->
+ <integer name="config_pauseRotationWhenUnfolding_hingeEventTimeout">0</integer>
+
+ <!-- Amount of time during which autorotation will be disabled since last display switch -->
+ <integer name="config_pauseRotationWhenUnfolding_displaySwitchTimeout">0</integer>
+
<!-- When a device enters any of these states, it should be woken up. States are defined in
device_state_configuration.xml. -->
<integer-array name="config_deviceStatesOnWhichToWakeUp">
@@ -4447,7 +4461,8 @@
See android.credentials.CredentialManager
-->
- <string name="config_defaultCredentialProviderService" translatable="false"></string>
+ <string-array name="config_defaultCredentialProviderService" translatable="false">
+ </string-array>
<!-- The package name for the system's smartspace service.
This service returns smartspace results.
@@ -6296,10 +6311,6 @@
-->
<integer name="config_deviceStateRearDisplay">-1</integer>
- <!-- Whether the lock screen is allowed to run its own live wallpaper,
- different from the home screen wallpaper. -->
- <bool name="config_independentLockscreenLiveWallpaper">false</bool>
-
<!-- Device state that corresponds to concurrent display mode where the default display
is the internal display. Public API for the feature is provided through Jetpack
WindowManager.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3ee8af2..8231407 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5369,15 +5369,11 @@
<string name="app_suspended_unsuspend_message">Unpause app</string>
<!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
- <string name="work_mode_off_title">Turn on work apps?</string>
- <!-- Text in a dialog. This text is confirming that the user wants to turn on access to their work apps and notifications, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=NONE] -->
- <string name="work_mode_off_message">Get access to your work apps and notifications</string>
- <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
- <string name="work_mode_turn_on">Turn on</string>
+ <string name="work_mode_off_title">Unpause work apps?</string>
+ <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+ <string name="work_mode_turn_on">Unpause</string>
<!-- Title for button to launch the personal safety app to make an emergency call -->
<string name="work_mode_emergency_call_button">Emergency</string>
- <!-- Text shown in a dialog when the user tries to launch a disabled work profile app when work apps are paused-->
- <string name="work_mode_dialer_off_message">Get access to your work apps and calls</string>
<!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
<string name="app_blocked_title">App is not available</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8855d5b..7b582da 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3754,7 +3754,7 @@
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultCredentialManagerHybridService" />
- <java-symbol type="string" name="config_defaultCredentialProviderService" />
+ <java-symbol type="array" name="config_defaultCredentialProviderService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
<java-symbol type="string" name="config_defaultSmartspaceService" />
<java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
@@ -4017,6 +4017,10 @@
<java-symbol type="array" name="config_halfFoldedDeviceStates" />
<java-symbol type="array" name="config_rearDisplayDeviceStates" />
<java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
+ <java-symbol type="bool" name="config_windowManagerPauseRotationWhenUnfolding" />
+ <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_hingeEventTimeout" />
+ <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_maxHingeAngle" />
+ <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_displaySwitchTimeout" />
<java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
<java-symbol type="string" name="config_foldedArea" />
@@ -4956,7 +4960,6 @@
<java-symbol type="string" name="concurrent_display_notification_power_save_content"/>
<java-symbol type="string" name="device_state_notification_turn_off_button"/>
<java-symbol type="string" name="device_state_notification_settings_button"/>
- <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
<java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index a64bb21..e59b259 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -4034,47 +4034,47 @@
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
@@ -4114,47 +4114,47 @@
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
@@ -4186,47 +4186,47 @@
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
@@ -4357,47 +4357,47 @@
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java
new file mode 100644
index 0000000..e20258a
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class that tests the APIs of DeviceConfigServiceManager.ServiceRegisterer.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DeviceConfigServiceManagerTest {
+
+ private static final String SERVICE_NAME = "device_config_updatable";
+ private DeviceConfigServiceManager.ServiceRegisterer mRegisterer;
+
+ @Before
+ public void setUp() {
+ mRegisterer = new DeviceConfigServiceManager.ServiceRegisterer(SERVICE_NAME);
+ }
+
+ @Test
+ public void testGetOrThrow() throws DeviceConfigServiceManager.ServiceNotFoundException {
+ if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+ assertThat(mRegisterer.getOrThrow()).isNotNull();
+ } else {
+ assertThrows(DeviceConfigServiceManager.ServiceNotFoundException.class,
+ mRegisterer::getOrThrow);
+ }
+ }
+
+ @Test
+ public void testGet() {
+ assumeTrue(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+ assertThat(mRegisterer.get()).isNotNull();
+ }
+
+ @Test
+ public void testTryGet() {
+ if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+ assertThat(mRegisterer.tryGet()).isNotNull();
+ } else {
+ assertThat(mRegisterer.tryGet()).isNull();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 8459330..212cc44 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
@DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
public void testGetBestDateTimePattern_enableDuplicateField() {
// en-US uses 12-hour format by default.
- assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
- assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+ assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+ assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
}
private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 39ed82ef..381c051 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
final long hourDuration = 2 * 60 * 60 * 1000;
- assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
+ assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
java.text.DateFormat.FULL));
- assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
- assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
- assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
- assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2afd54b..c106854 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -883,6 +883,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "-1258739769": {
+ "message": "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"-1257821162": {
"message": "OUT SURFACE %s: copied",
"level": "INFO",
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 852edef..f0ed6ee 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
index c400dc6..029d838 100644
--- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
@@ -14,686 +14,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="30dp" android:width="30dp" android:viewportHeight="30"
- android:viewportWidth="30">
- <group android:name="_R_G" android:scaleX="-1" android:translateX="30">
- <group android:name="_R_G_L_0_G" android:translateX="-135" android:translateY="-135"
- android:pivotX="150" android:pivotY="150" android:scaleX="0.1"
- android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_1_G" android:translateX="134.624"
- android:translateY="87.514" android:pivotX="11.625" android:pivotY="6.39"
- android:scaleX="10" android:scaleY="10">
- <group android:name="_R_G_L_0_G_L_1_G_D_0_P_0_G_0_T_0"
- android:translateX="11.625" android:translateY="6.464"
- android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_0_G_L_1_G_D_0_P_0"
- android:fillColor="@color/letterbox_reachability_education_item_color"
- android:fillAlpha="1"
- android:fillType="nonZero"
- android:pathData=" M-1.54 5.39 C-3.87,4.71 -5.49,2.54 -5.49,0.11 C-5.49,-2.92 -3.03,-5.38 0,-5.38 C3.03,-5.38 5.49,-2.92 5.49,0.11 C5.49,2.11 4.41,3.95 2.66,4.92 C2.66,4.92 1.69,3.17 1.69,3.17 C2.8,2.55 3.49,1.38 3.49,0.11 C3.49,-1.82 1.93,-3.38 0,-3.38 C-1.93,-3.38 -3.49,-1.82 -3.49,0.11 C-3.49,1.65 -2.46,3.03 -0.98,3.47 C-0.98,3.47 -1.54,5.39 -1.54,5.39c "/>
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="138"
- android:translateY="138" android:pivotX="12" android:pivotY="12"
- android:scaleX="10" android:scaleY="10">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0"
- android:fillColor="@color/letterbox_reachability_education_item_color"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="fillAlpha" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="750" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="1083" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="2250" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="2583" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="3750" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="4083" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="5250" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="5583" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="1083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="1083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="2250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="2250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="2583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="2583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="3750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="3750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="4083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="4083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="5250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="5250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="5583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="5583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="pathData" android:duration="500"
- android:startOffset="0"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="500"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="750"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="833"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="1083"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="1167"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="2000"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="2250"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="2333"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="2583"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="2667"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="3500"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="3750"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="3833"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="4083"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="4167"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="5000"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="5250"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="5333"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="5583"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="6000"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <group android:scaleX="-1" android:translateX="960">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
index a807a77..592f899 100644
--- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
@@ -14,686 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="30dp" android:width="30dp" android:viewportHeight="30"
- android:viewportWidth="30">
- <group android:name="_R_G">
- <group android:name="_R_G_L_0_G" android:translateX="-135" android:translateY="-135"
- android:pivotX="150" android:pivotY="150" android:scaleX="0.1"
- android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_1_G" android:translateX="134.624"
- android:translateY="87.514" android:pivotX="11.625" android:pivotY="6.39"
- android:scaleX="10" android:scaleY="10">
- <group android:name="_R_G_L_0_G_L_1_G_D_0_P_0_G_0_T_0"
- android:translateX="11.625" android:translateY="6.464"
- android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_0_G_L_1_G_D_0_P_0"
- android:fillColor="@color/letterbox_reachability_education_item_color"
- android:fillAlpha="1"
- android:fillType="nonZero"
- android:pathData=" M-1.54 5.39 C-3.87,4.71 -5.49,2.54 -5.49,0.11 C-5.49,-2.92 -3.03,-5.38 0,-5.38 C3.03,-5.38 5.49,-2.92 5.49,0.11 C5.49,2.11 4.41,3.95 2.66,4.92 C2.66,4.92 1.69,3.17 1.69,3.17 C2.8,2.55 3.49,1.38 3.49,0.11 C3.49,-1.82 1.93,-3.38 0,-3.38 C-1.93,-3.38 -3.49,-1.82 -3.49,0.11 C-3.49,1.65 -2.46,3.03 -0.98,3.47 C-0.98,3.47 -1.54,5.39 -1.54,5.39c "/>
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="138"
- android:translateY="138" android:pivotX="12" android:pivotY="12"
- android:scaleX="10" android:scaleY="10">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0"
- android:fillColor="@color/letterbox_reachability_education_item_color"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="fillAlpha" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="750" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="1083" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="2250" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="2583" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="3750" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="4083" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="5250" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="0.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="fillAlpha" android:duration="83"
- android:startOffset="5583" android:valueFrom="0.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator
- android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="500"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="1083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="1083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="1167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="2000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="2250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="2250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="2333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="2583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="2583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="2667" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="3500" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="3750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="3750" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="3833" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="4083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="4083" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="833"
- android:startOffset="4167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="5000" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="5250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="5250" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="250"
- android:startOffset="5333" android:valueFrom="1"
- android:valueTo="1.4000000000000001" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="83"
- android:startOffset="5583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="83"
- android:startOffset="5583" android:valueFrom="1.4000000000000001"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.999,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="pathData" android:duration="500"
- android:startOffset="0"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="500"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="750"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="833"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="1083"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="1167"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="2000"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="2250"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="2333"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="2583"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="2667"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="3500"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="3750"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="3833"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="4083"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="833"
- android:startOffset="4167"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="5000"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="5250"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="250"
- android:startOffset="5333"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="pathData" android:duration="83"
- android:startOffset="5583"
- android:valueFrom="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,8 14.13,8 C14.13,7.3 13.88,6.71 13.4,6.23 C12.92,5.74 12.33,5.5 11.63,5.5 C10.93,5.5 10.33,5.74 9.85,6.23 C9.37,6.71 9.13,7.3 9.13,8 C9.13,8 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,8 11.13,8 C11.13,7.85 11.17,7.73 11.26,7.64 C11.35,7.55 11.48,7.5 11.63,7.5 C11.78,7.5 11.9,7.55 11.99,7.64 C12.08,7.73 12.13,7.85 12.13,8 C12.13,8 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueTo="M19.81 13.64 C19.62,13.23 19.33,12.93 18.93,12.75 C18.93,12.75 15.23,10.95 15.23,10.95 C15.16,10.9 15.09,10.86 15.01,10.84 C14.99,10.83 14.96,10.83 14.94,10.83 C14.88,10.81 14.83,10.8 14.78,10.8 C14.78,10.8 14.13,10.8 14.13,10.8 C14.13,10.8 14.13,8.9 14.13,8.9 C14.13,8.9 14.13,6.5 14.13,6.5 C14.13,5.8 13.88,5.21 13.4,4.72 C12.92,4.24 12.33,4 11.63,4 C10.93,4 10.33,4.24 9.85,4.72 C9.37,5.21 9.13,5.8 9.13,6.5 C9.13,6.5 9.13,8.95 9.13,8.95 C9.13,8.95 9.13,11.4 9.13,11.4 C9.13,11.4 9.13,14.65 9.13,14.65 C9.13,14.65 7.18,14.2 7.18,14.2 C6.86,14.12 6.56,14.14 6.26,14.26 C5.97,14.39 5.71,14.57 5.48,14.8 C5.48,14.8 4.08,16.25 4.08,16.25 C4.08,16.25 9.23,21.4 9.23,21.4 C9.41,21.58 9.63,21.73 9.88,21.84 C10.13,21.95 10.39,22 10.68,22 C10.68,22 17.08,22 17.08,22 C17.56,22 17.99,21.85 18.38,21.54 C18.76,21.23 18.99,20.83 19.08,20.35 C19.08,20.35 19.98,14.9 19.98,14.9 C20.06,14.47 20,14.05 19.81,13.64c M17.08 20 C17.08,20 10.68,20 10.68,20 C10.68,20 6.88,16.2 6.88,16.2 C6.88,16.2 11.13,17.1 11.13,17.1 C11.13,17.1 11.13,6.5 11.13,6.5 C11.13,6.35 11.17,6.23 11.26,6.14 C11.35,6.05 11.48,6 11.63,6 C11.78,6 11.9,6.05 11.99,6.14 C12.08,6.23 12.13,6.35 12.13,6.5 C12.13,6.5 12.13,12.5 12.13,12.5 C12.13,12.5 13.88,12.5 13.88,12.5 C13.88,12.5 18.02,14.55 18.02,14.55 C18.02,14.55 17.08,20 17.08,20c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="6000"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
index 1e36fb6..49491a7 100644
--- a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
@@ -22,48 +22,44 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.wm.shell.compatui.ReachabilityEduHandLayout
+ <androidx.appcompat.widget.AppCompatTextView
style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_up_button"
- android:maxWidth="@dimen/letterbox_reachability_education_item_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- <com.android.wm.shell.compatui.ReachabilityEduHandLayout
+ <androidx.appcompat.widget.AppCompatTextView
style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_vertical|right"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_right_button"
- android:maxWidth="@dimen/letterbox_reachability_education_item_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- <com.android.wm.shell.compatui.ReachabilityEduHandLayout
+ <androidx.appcompat.widget.AppCompatTextView
style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_left_hand"
android:layout_gravity="center_vertical|left"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_left_button"
- android:maxWidth="@dimen/letterbox_reachability_education_item_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- <com.android.wm.shell.compatui.ReachabilityEduHandLayout
+ <androidx.appcompat.widget.AppCompatTextView
style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_down_button"
- android:maxWidth="@dimen/letterbox_reachability_education_item_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 4b885c2..c487e4a 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -45,9 +45,6 @@
<!-- Letterbox Dialog -->
<color name="letterbox_dialog_background">@android:color/system_neutral1_900</color>
- <!-- Reachability Education color for hand icon and text-->
- <color name="letterbox_reachability_education_item_color">#BFC8CC</color>
-
<!-- GM2 colors -->
<color name="GM2_grey_200">#E8EAED</color>
<color name="GM2_grey_700">#5F6368</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 75e0d83..2be34c9 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -334,9 +334,6 @@
<!-- The margin between the reachability dialog container and its parent. -->
<dimen name="letterbox_reachability_education_dialog_margin">16dp</dimen>
- <!-- The width of each item in the reachability education -->
- <dimen name="letterbox_reachability_education_item_width">118dp</dimen>
-
<!-- The size of the icon in the item of reachability education -->
<dimen name="letterbox_reachability_education_item_image_size">24dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 8cad385..ee80c472 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -151,7 +151,7 @@
</item>
</style>
- <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat">
+ <style name="ReachabilityEduHandLayout">
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:background">@android:color/transparent</item>
@@ -160,7 +160,7 @@
<item name="android:lineSpacingExtra">-1sp</item>
<item name="android:textSize">12sp</item>
<item name="android:textAlignment">center</item>
- <item name="android:textColor">@color/letterbox_reachability_education_item_color</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textAppearance">
@*android:style/TextAppearance.DeviceDefault.Body2
</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 8f364b4..026ea069 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -47,6 +47,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
deleted file mode 100644
index 56b13b8..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating app badge icons that are shown on bubbles.
- */
-public class BubbleBadgeIconFactory extends BaseIconFactory {
-
- public BubbleBadgeIconFactory(Context context) {
- super(context, context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size));
- }
-
- /**
- * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
- * will include the workprofile indicator on the badge if appropriate.
- */
- BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
- if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
- AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon;
- userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(), ad.getForeground());
- }
- if (isImportantConversation) {
- userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
- }
- Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1, MODE_WITH_SHADOW);
- return createIconBitmap(userBadgedBitmap);
- }
-
- private class CircularRingDrawable extends CircularAdaptiveIcon {
-
- final int mImportantConversationColor;
- final int mRingWidth;
- final Rect mInnerBounds = new Rect();
-
- final Drawable mDr;
-
- CircularRingDrawable(Drawable dr) {
- super(null, null);
- mDr = dr;
- mImportantConversationColor = mContext.getResources().getColor(
- R.color.important_conversation, null);
- mRingWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width);
- }
-
- @Override
- public void draw(Canvas canvas) {
- int save = canvas.save();
- canvas.clipPath(getIconMask());
- canvas.drawColor(mImportantConversationColor);
- mInnerBounds.set(getBounds());
- mInnerBounds.inset(mRingWidth, mRingWidth);
- canvas.translate(mInnerBounds.left, mInnerBounds.top);
- mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
- mDr.draw(canvas);
- canvas.restoreToCount(save);
- }
- }
-
- private static class CircularAdaptiveIcon extends AdaptiveIconDrawable {
-
- final Path mPath = new Path();
-
- CircularAdaptiveIcon(Drawable bg, Drawable fg) {
- super(bg, fg);
- }
-
- @Override
- public Path getIconMask() {
- mPath.reset();
- Rect bounds = getBounds();
- mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
- return mPath;
- }
-
- @Override
- public void draw(Canvas canvas) {
- int save = canvas.save();
- canvas.clipPath(getIconMask());
-
- Drawable d;
- if ((d = getBackground()) != null) {
- d.draw(canvas);
- }
- if ((d = getForeground()) != null) {
- d.draw(canvas);
- }
- canvas.restoreToCount(save);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index da8eb47..fd66153 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -89,6 +89,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -317,8 +320,13 @@
mBubblePositioner = positioner;
mBubbleData = data;
mSavedUserBubbleData = new SparseArray<>();
- mBubbleIconFactory = new BubbleIconFactory(context);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
+ mBubbleIconFactory = new BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
mOneHandedOptional = oneHandedOptional;
@@ -927,8 +935,13 @@
if (mStackView != null) {
mStackView.onThemeChanged();
}
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
// Reload each bubble
for (Bubble b : mBubbleData.getBubbles()) {
@@ -964,8 +977,13 @@
mDensityDpi = newConfig.densityDpi;
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
mBubbleData.onMaxBubblesChanged();
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+ mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
}
if (newConfig.fontScale != mFontScale) {
@@ -1052,7 +1070,27 @@
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow.
*
- * This is currently only used when opening a bubble via clicking on a conversation widget.
+ * This is used by external callers (launcher).
+ */
+ public void expandStackAndSelectBubbleFromLauncher(String key) {
+ Bubble b = mBubbleData.getAnyBubbleWithkey(key);
+ if (b == null) {
+ return;
+ }
+ if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
+ // already in the stack
+ mBubbleData.setSelectedBubbleFromLauncher(b);
+ mLayerView.showExpandedView(b);
+ } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
+ // TODO: (b/271468319) handle overflow
+ } else {
+ Log.w(TAG, "didn't add bubble from launcher: " + key);
+ }
+ }
+
+ /**
+ * Expands and selects the provided bubble as long as it already exists in the stack or the
+ * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
*/
public void expandStackAndSelectBubble(Bubble b) {
if (b == null) {
@@ -1703,6 +1741,14 @@
// Update the cached state for queries from SysUI
mImpl.mCachedState.update(update);
+
+ if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+ BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
+ // Some updates aren't relevant to the bubble bar so check first.
+ if (bubbleBarUpdate.anythingChanged()) {
+ mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+ }
+ }
}
};
@@ -1972,17 +2018,20 @@
@Override
public void showBubble(String key, boolean onLauncherHome) {
- // TODO
+ mMainExecutor.execute(() -> {
+ mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
+ mController.expandStackAndSelectBubbleFromLauncher(key);
+ });
}
@Override
public void removeBubble(String key, int reason) {
- // TODO
+ // TODO (b/271466616) allow removals from launcher
}
@Override
public void collapseBubbles() {
- // TODO
+ mMainExecutor.execute(() -> mController.collapseStack());
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index a26c0c4..f9cf9d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -334,6 +334,35 @@
dispatchPendingChanges();
}
+ /**
+ * Sets the selected bubble and expands it, but doesn't dispatch changes
+ * to {@link BubbleData.Listener}. This is used for updates coming from launcher whose views
+ * will already be updated so we don't need to notify them again, but BubbleData should be
+ * updated to have the correct state.
+ */
+ public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
+ }
+ mExpanded = true;
+ if (Objects.equals(bubble, mSelectedBubble)) {
+ return;
+ }
+ boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
+ if (bubble != null
+ && !mBubbles.contains(bubble)
+ && !mOverflowBubbles.contains(bubble)
+ && !isOverflow) {
+ Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ + " (" + bubble + ") bubbles=" + mBubbles);
+ return;
+ }
+ if (bubble != null && !isOverflow) {
+ ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
+ }
+ mSelectedBubble = bubble;
+ }
+
public void setSelectedBubble(BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
deleted file mode 100644
index 4ded3ea..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 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.bubbles;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating normalized bubble icons.
- * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
- * so there is no need to manage a pool across multiple threads.
- */
-@VisibleForTesting
-public class BubbleIconFactory extends BaseIconFactory {
-
- public BubbleIconFactory(Context context) {
- super(context, context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
- }
-
- /**
- * Returns the drawable that the developer has provided to display in the bubble.
- */
- Drawable getBubbleDrawable(@NonNull final Context context,
- @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
- if (shortcutInfo != null) {
- LauncherApps launcherApps =
- (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- int density = context.getResources().getConfiguration().densityDpi;
- return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
- } else {
- if (ic != null) {
- if (ic.getType() == Icon.TYPE_URI
- || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
- context.grantUriPermission(context.getPackageName(),
- ic.getUri(),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return ic.loadDrawable(context);
- }
- return null;
- }
- }
-
- /**
- * Creates the bitmap for the provided drawable and returns the scale used for
- * drawing the actual drawable.
- */
- public Bitmap createIconBitmap(@NonNull Drawable icon, float[] outScale) {
- if (outScale == null) {
- outScale = new float[1];
- }
- icon = normalizeAndWrapToAdaptiveIcon(icon,
- true /* shrinkNonAdaptiveIcons */,
- null /* outscale */,
- outScale);
- return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 6cdb80b..c2a05b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -28,6 +28,7 @@
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.FrameLayout
+import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -93,7 +94,8 @@
val shapeColor = res.getColor(android.R.color.system_accent1_1000)
overflowBtn?.iconDrawable?.setTint(shapeColor)
- val iconFactory = BubbleIconFactory(context)
+ val iconFactory = BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_size))
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 1a97c05..d1081de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -42,6 +42,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 4e10ce8..09d99b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -58,11 +58,18 @@
"has_seen_letterbox_education";
/**
- * Key prefix for the {@link SharedPreferences} entries related to the reachability
+ * Key prefix for the {@link SharedPreferences} entries related to the horizontal
+ * reachability education.
+ */
+ private static final String HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_horizontal_reachability_education";
+
+ /**
+ * Key prefix for the {@link SharedPreferences} entries related to the vertical reachability
* education.
*/
- private static final String HAS_SEEN_REACHABILITY_EDUCATION_KEY_PREFIX =
- "has_seen_reachability_education";
+ private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
+ "has_seen_vertical_reachability_education";
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
@@ -129,14 +136,6 @@
}
/**
- * @return {@value true} if the reachability education is enabled.
- */
- boolean isReachabilityEducationEnabled() {
- return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
- && mIsLetterboxReachabilityEducationAllowed);
- }
-
- /**
* Enables/Disables the reachability education
*/
void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
@@ -145,33 +144,48 @@
void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
mCompatUISharedPreferences.edit().putBoolean(
- getDontShowAgainRestartKey(taskInfo.userId, taskInfo.topActivity.getPackageName()),
+ dontShowAgainRestartKey(taskInfo.userId, taskInfo.topActivity.getPackageName()),
true).apply();
}
boolean shouldShowRestartDialogAgain(TaskInfo taskInfo) {
- return !mCompatUISharedPreferences.getBoolean(getDontShowAgainRestartKey(taskInfo.userId,
+ return !mCompatUISharedPreferences.getBoolean(dontShowAgainRestartKey(taskInfo.userId,
taskInfo.topActivity.getPackageName()), /* default= */ false);
}
- void setDontShowReachabilityEducationAgain(TaskInfo taskInfo) {
+ void setUserHasSeenHorizontalReachabilityEducation(TaskInfo taskInfo) {
mCompatUISharedPreferences.edit().putBoolean(
- getDontShowAgainReachabilityEduKey(taskInfo.userId), true).apply();
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ void setUserHasSeenVerticalReachabilityEducation(TaskInfo taskInfo) {
+ mCompatUISharedPreferences.edit().putBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), true).apply();
+ }
+
+ boolean hasSeenHorizontalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenHorizontalReachabilityEduKey(taskInfo.userId), /* default= */false);
+ }
+
+ boolean hasSeenVerticalReachabilityEducation(@NonNull TaskInfo taskInfo) {
+ return mCompatUISharedPreferences.getBoolean(
+ hasSeenVerticalReachabilityEduKey(taskInfo.userId), /* default= */false);
}
boolean shouldShowReachabilityEducation(@NonNull TaskInfo taskInfo) {
- return getHasSeenLetterboxEducation(taskInfo.userId)
- && !mCompatUISharedPreferences.getBoolean(
- getDontShowAgainReachabilityEduKey(taskInfo.userId), /* default= */false);
+ return isReachabilityEducationEnabled()
+ && (!hasSeenHorizontalReachabilityEducation(taskInfo)
+ || !hasSeenVerticalReachabilityEducation(taskInfo));
}
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
- .getBoolean(getDontShowLetterboxEduKey(userId), /* default= */ false);
+ .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
}
void setSeenLetterboxEducation(int userId) {
- mLetterboxEduSharedPreferences.edit().putBoolean(getDontShowLetterboxEduKey(userId),
+ mLetterboxEduSharedPreferences.edit().putBoolean(dontShowLetterboxEduKey(userId),
true).apply();
}
@@ -204,15 +218,24 @@
}
}
- private static String getDontShowAgainReachabilityEduKey(int userId) {
- return HAS_SEEN_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ private boolean isReachabilityEducationEnabled() {
+ return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+ && mIsLetterboxReachabilityEducationAllowed);
}
- private static String getDontShowLetterboxEduKey(int userId) {
+ private static String hasSeenHorizontalReachabilityEduKey(int userId) {
+ return HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String hasSeenVerticalReachabilityEduKey(int userId) {
+ return HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
+ }
+
+ private static String dontShowLetterboxEduKey(int userId) {
return String.valueOf(userId);
}
- private String getDontShowAgainRestartKey(int userId, String packageName) {
+ private String dontShowAgainRestartKey(int userId, String packageName) {
return packageName + "@" + userId;
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 4d83247..838e37a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -201,7 +201,9 @@
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
- createOrUpdateReachabilityEduLayout(taskInfo, taskListener, false);
+ if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ }
}
@Override
@@ -370,7 +372,7 @@
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mActiveLetterboxEduLayout = null;
// We need to update the UI
- createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second, true);
+ createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
}
private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
@@ -429,9 +431,8 @@
}
private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener, boolean forceUpdate) {
+ ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
- mActiveReachabilityEduLayout.forceUpdate(forceUpdate);
// UI already exists, update the UI layout.
if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
@@ -463,7 +464,7 @@
ReachabilityEduWindowManager createReachabilityEduWindowManager(Context context,
TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
- return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue, mCallback,
+ return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
mCompatUIConfiguration, mMainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduHandLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduHandLayout.java
deleted file mode 100644
index 6081ef1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduHandLayout.java
+++ /dev/null
@@ -1,66 +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.compatui;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.appcompat.widget.AppCompatTextView;
-
-/**
- * Custom layout for Reachability Education hand.
- */
-public class ReachabilityEduHandLayout extends AppCompatTextView {
-
- private Drawable mHandDrawable;
-
- public ReachabilityEduHandLayout(Context context) {
- this(context, null);
- }
-
- public ReachabilityEduHandLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ReachabilityEduHandLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mHandDrawable = getCompoundDrawables()[/* top */ 1];
- }
-
- void hide() {
- stopAnimation();
- setAlpha(0);
- setVisibility(View.INVISIBLE);
- }
-
- void startAnimation() {
- if (mHandDrawable instanceof Animatable) {
- final Animatable animatedBg = (Animatable) mHandDrawable;
- animatedBg.start();
- }
- }
-
- void stopAnimation() {
- if (mHandDrawable instanceof Animatable) {
- final Animatable animatedBg = (Animatable) mHandDrawable;
- animatedBg.stop();
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
index 6a72d28..dc3d1d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduLayout.java
@@ -16,14 +16,16 @@
package com.android.wm.shell.compatui;
+import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
+
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.TaskInfo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
import android.widget.FrameLayout;
import com.android.wm.shell.R;
@@ -40,27 +42,23 @@
private static final float ALPHA_FULL_OPAQUE = 1f;
- private static final long VISIBILITY_SHOW_ANIMATION_DURATION_MS = 167;
-
- private static final long VISIBILITY_SHOW_ANIMATION_DELAY_MS = 250;
-
- private static final long VISIBILITY_SHOW_DOUBLE_TAP_ANIMATION_DELAY_MS = 80;
+ private static final long VISIBILITY_ANIMATION_DURATION_MS = 400;
private static final long MARGINS_ANIMATION_DURATION_MS = 250;
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
private ReachabilityEduWindowManager mWindowManager;
- private ReachabilityEduHandLayout mMoveLeftButton;
- private ReachabilityEduHandLayout mMoveRightButton;
- private ReachabilityEduHandLayout mMoveUpButton;
- private ReachabilityEduHandLayout mMoveDownButton;
+ private View mMoveLeftButton;
+ private View mMoveRightButton;
+ private View mMoveUpButton;
+ private View mMoveDownButton;
- private int mLastLeftMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- private int mLastRightMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- private int mLastTopMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- private int mLastBottomMargin = TaskInfo.PROPERTY_VALUE_UNSET;
-
- private boolean mIsLayoutActive;
+ private int mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ private int mLastRightMargin = PROPERTY_VALUE_UNSET;
+ private int mLastTopMargin = PROPERTY_VALUE_UNSET;
+ private int mLastBottomMargin = PROPERTY_VALUE_UNSET;
public ReachabilityEduLayout(Context context) {
this(context, null);
@@ -83,36 +81,29 @@
mWindowManager = windowManager;
}
- void handleVisibility(boolean isActivityLetterboxed, int letterboxVerticalPosition,
+ void handleVisibility(boolean horizontalEnabled, boolean verticalEnabled,
+ int letterboxVerticalPosition,
int letterboxHorizontalPosition, int availableWidth, int availableHeight,
- boolean isDoubleTap) {
- // If the app is not letterboxed we hide all the buttons.
- if (!mIsLayoutActive || !isActivityLetterboxed || (
- letterboxHorizontalPosition == TaskInfo.PROPERTY_VALUE_UNSET
- && letterboxVerticalPosition == TaskInfo.PROPERTY_VALUE_UNSET)) {
- hideAllImmediately();
- } else if (letterboxHorizontalPosition != TaskInfo.PROPERTY_VALUE_UNSET) {
- handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition,
- isDoubleTap);
- } else {
- handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition,
- isDoubleTap);
+ CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo) {
+ hideAllImmediately();
+ if (horizontalEnabled && letterboxHorizontalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition);
+ compatUIConfiguration.setUserHasSeenHorizontalReachabilityEducation(taskInfo);
+ } else if (verticalEnabled && letterboxVerticalPosition != PROPERTY_VALUE_UNSET) {
+ handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition);
+ compatUIConfiguration.setUserHasSeenVerticalReachabilityEducation(taskInfo);
}
}
void hideAllImmediately() {
- mMoveLeftButton.hide();
- mMoveRightButton.hide();
- mMoveUpButton.hide();
- mMoveDownButton.hide();
- mLastLeftMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- mLastRightMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- mLastTopMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- mLastBottomMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- }
-
- void setIsLayoutActive(boolean isLayoutActive) {
- this.mIsLayoutActive = isLayoutActive;
+ hideImmediately(mMoveLeftButton);
+ hideImmediately(mMoveRightButton);
+ hideImmediately(mMoveUpButton);
+ hideImmediately(mMoveDownButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
}
@Override
@@ -128,6 +119,11 @@
mMoveDownButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
}
+ private void hideImmediately(View view) {
+ view.setAlpha(0);
+ view.setVisibility(View.INVISIBLE);
+ }
+
private Animator marginAnimator(View view, Function<LayoutParams, Integer> marginSupplier,
BiConsumer<LayoutParams, Integer> marginConsumer, int from, int to) {
final LayoutParams layoutParams = ((LayoutParams) view.getLayoutParams());
@@ -141,11 +137,11 @@
}
private void handleLetterboxHorizontalPosition(int availableWidth,
- int letterboxHorizontalPosition, boolean isDoubleTap) {
- mMoveUpButton.hide();
- mMoveDownButton.hide();
- mLastTopMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- mLastBottomMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ int letterboxHorizontalPosition) {
+ hideItem(mMoveUpButton);
+ hideItem(mMoveDownButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
// We calculate the available space on the left and right
final int horizontalGap = availableWidth / 2;
final int leftAvailableSpace = letterboxHorizontalPosition * horizontalGap;
@@ -153,7 +149,7 @@
// We show the button if we have enough space
if (leftAvailableSpace >= mMoveLeftButton.getMeasuredWidth()) {
int newLeftMargin = (horizontalGap - mMoveLeftButton.getMeasuredWidth()) / 2;
- if (mLastLeftMargin == TaskInfo.PROPERTY_VALUE_UNSET) {
+ if (mLastLeftMargin == PROPERTY_VALUE_UNSET) {
mLastLeftMargin = newLeftMargin;
}
if (mLastLeftMargin != newLeftMargin) {
@@ -165,14 +161,14 @@
leftParams.leftMargin = mLastLeftMargin;
mMoveLeftButton.setLayoutParams(leftParams);
}
- showItem(mMoveLeftButton, isDoubleTap);
+ showItem(mMoveLeftButton);
} else {
- mMoveLeftButton.hide();
- mLastLeftMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ hideItem(mMoveLeftButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
}
if (rightAvailableSpace >= mMoveRightButton.getMeasuredWidth()) {
int newRightMargin = (horizontalGap - mMoveRightButton.getMeasuredWidth()) / 2;
- if (mLastRightMargin == TaskInfo.PROPERTY_VALUE_UNSET) {
+ if (mLastRightMargin == PROPERTY_VALUE_UNSET) {
mLastRightMargin = newRightMargin;
}
if (mLastRightMargin != newRightMargin) {
@@ -185,26 +181,26 @@
rightParams.rightMargin = mLastRightMargin;
mMoveRightButton.setLayoutParams(rightParams);
}
- showItem(mMoveRightButton, isDoubleTap);
+ showItem(mMoveRightButton);
} else {
- mMoveRightButton.hide();
- mLastRightMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ hideItem(mMoveRightButton);
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
}
}
private void handleLetterboxVerticalPosition(int availableHeight,
- int letterboxVerticalPosition, boolean isDoubleTap) {
- mMoveLeftButton.hide();
- mMoveRightButton.hide();
- mLastLeftMargin = TaskInfo.PROPERTY_VALUE_UNSET;
- mLastRightMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ int letterboxVerticalPosition) {
+ hideItem(mMoveLeftButton);
+ hideItem(mMoveRightButton);
+ mLastLeftMargin = PROPERTY_VALUE_UNSET;
+ mLastRightMargin = PROPERTY_VALUE_UNSET;
// We calculate the available space on the left and right
final int verticalGap = availableHeight / 2;
final int topAvailableSpace = letterboxVerticalPosition * verticalGap;
final int bottomAvailableSpace = availableHeight - topAvailableSpace;
if (topAvailableSpace >= mMoveUpButton.getMeasuredHeight()) {
int newTopMargin = (verticalGap - mMoveUpButton.getMeasuredHeight()) / 2;
- if (mLastTopMargin == TaskInfo.PROPERTY_VALUE_UNSET) {
+ if (mLastTopMargin == PROPERTY_VALUE_UNSET) {
mLastTopMargin = newTopMargin;
}
if (mLastTopMargin != newTopMargin) {
@@ -216,14 +212,14 @@
topParams.topMargin = mLastTopMargin;
mMoveUpButton.setLayoutParams(topParams);
}
- showItem(mMoveUpButton, isDoubleTap);
+ showItem(mMoveUpButton);
} else {
- mMoveUpButton.hide();
- mLastTopMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ hideItem(mMoveUpButton);
+ mLastTopMargin = PROPERTY_VALUE_UNSET;
}
if (bottomAvailableSpace >= mMoveDownButton.getMeasuredHeight()) {
int newBottomMargin = (verticalGap - mMoveDownButton.getMeasuredHeight()) / 2;
- if (mLastBottomMargin == TaskInfo.PROPERTY_VALUE_UNSET) {
+ if (mLastBottomMargin == PROPERTY_VALUE_UNSET) {
mLastBottomMargin = newBottomMargin;
}
if (mLastBottomMargin != newBottomMargin) {
@@ -236,43 +232,38 @@
bottomParams.bottomMargin = mLastBottomMargin;
mMoveDownButton.setLayoutParams(bottomParams);
}
- showItem(mMoveDownButton, isDoubleTap);
+ showItem(mMoveDownButton);
} else {
- mMoveDownButton.hide();
- mLastBottomMargin = TaskInfo.PROPERTY_VALUE_UNSET;
+ hideItem(mMoveDownButton);
+ mLastBottomMargin = PROPERTY_VALUE_UNSET;
}
}
- private void showItem(ReachabilityEduHandLayout view, boolean fromDoubleTap) {
- if (view.getVisibility() == View.VISIBLE) {
- // Already visible we just start animation
- view.startAnimation();
- return;
- }
+ private void showItem(View view) {
view.setVisibility(View.VISIBLE);
- final long delay = fromDoubleTap ? VISIBILITY_SHOW_DOUBLE_TAP_ANIMATION_DELAY_MS
- : VISIBILITY_SHOW_ANIMATION_DELAY_MS;
- AlphaAnimation alphaAnimation = new AlphaAnimation(ALPHA_FULL_TRANSPARENT,
- ALPHA_FULL_OPAQUE);
- alphaAnimation.setDuration(VISIBILITY_SHOW_ANIMATION_DURATION_MS);
- alphaAnimation.setStartOffset(delay);
- alphaAnimation.setFillAfter(true);
- alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
+ ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeIn.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- // We trigger the hand animation
- view.setAlpha(ALPHA_FULL_OPAQUE);
- view.startAnimation();
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.VISIBLE);
}
});
- view.startAnimation(alphaAnimation);
+ fadeIn.start();
}
+
+ private void hideItem(View view) {
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
+ fadeOut.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ fadeOut.start();
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index f1b098e..b6e396d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -35,7 +35,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
/**
* Window manager for the reachability education
@@ -50,7 +49,8 @@
// The time to wait before hiding the education
private static final long DISAPPEAR_DELAY_MS = 4000L;
- private final CompatUICallback mCallback;
+ private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
+ private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
private final CompatUIConfiguration mCompatUIConfiguration;
@@ -86,11 +86,10 @@
ReachabilityEduLayout mLayout;
ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
- SyncTransactionQueue syncQueue, CompatUICallback callback,
+ SyncTransactionQueue syncQueue,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
- mCallback = callback;
mTaskInfo = taskInfo;
mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -118,8 +117,7 @@
@Override
protected boolean eligibleToShowLayout() {
- return mCompatUIConfiguration.isReachabilityEducationEnabled()
- && mIsActivityLetterboxed
+ return mIsActivityLetterboxed
&& (mLetterboxVerticalPosition != -1 || mLetterboxHorizontalPosition != -1);
}
@@ -155,12 +153,6 @@
mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
mHasUserDoubleTapped = taskInfo.isFromLetterboxDoubleTap;
- if (taskInfo.isFromLetterboxDoubleTap) {
- // In this case we disable the reachability for the following launch of
- // the current application. Anyway because a double tap event happened,
- // the reachability education is displayed
- mCompatUIConfiguration.setDontShowReachabilityEducationAgain(taskInfo);
- }
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
}
@@ -168,13 +160,12 @@
mHasLetterboxSizeChanged = prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
|| prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight;
- if (mForceUpdate || prevIsActivityLetterboxed != mIsActivityLetterboxed
+ if (mHasUserDoubleTapped || prevIsActivityLetterboxed != mIsActivityLetterboxed
|| prevLetterboxVerticalPosition != mLetterboxVerticalPosition
|| prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition
|| prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
|| prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight) {
updateVisibilityOfViews();
- mForceUpdate = false;
}
return true;
@@ -235,41 +226,41 @@
if (mLayout == null) {
return;
}
- if (shouldUpdateEducation()) {
- if (!mHasLetterboxSizeChanged) {
- mLayout.setIsLayoutActive(true);
- }
+
+ final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+ final boolean eligibleForDisplayVerticalEducation = mForceUpdate
+ || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(mTaskInfo)
+ || (mHasUserDoubleTapped
+ && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
+ || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
+
+ if (mIsActivityLetterboxed && (eligibleForDisplayHorizontalEducation
+ || eligibleForDisplayVerticalEducation)) {
int availableWidth = getTaskBounds().width() - mTopActivityLetterboxWidth;
int availableHeight = getTaskBounds().height() - mTopActivityLetterboxHeight;
- mLayout.handleVisibility(mIsActivityLetterboxed, mLetterboxVerticalPosition,
- mLetterboxHorizontalPosition, availableWidth, availableHeight,
- mHasUserDoubleTapped);
+ mLayout.handleVisibility(eligibleForDisplayHorizontalEducation,
+ eligibleForDisplayVerticalEducation,
+ mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth,
+ availableHeight, mCompatUIConfiguration, mTaskInfo);
if (!mHasLetterboxSizeChanged) {
updateHideTime();
mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
}
mHasUserDoubleTapped = false;
} else {
- hideReachability();
+ mLayout.hideAllImmediately();
}
}
private void hideReachability() {
- if (mLayout != null) {
- mLayout.setIsLayoutActive(false);
- }
if (mLayout == null || !shouldHideEducation()) {
return;
}
mLayout.hideAllImmediately();
- // We need this in case the icons disappear after the timeout without an explicit
- // double tap of the user.
- mCompatUIConfiguration.setDontShowReachabilityEducationAgain(mTaskInfo);
- }
-
- private boolean shouldUpdateEducation() {
- return mForceUpdate || mHasUserDoubleTapped || mHasLetterboxSizeChanged
- || mCompatUIConfiguration.shouldShowReachabilityEducation(mTaskInfo);
}
private boolean shouldHideEducation() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index d18e98a..248a5fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -18,6 +18,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.Context;
@@ -146,6 +148,13 @@
t.setScale(sc, currentScaleX, currentScaleY);
t.apply();
});
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
animator.start();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index 926cfb3..deb7c6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module desktop owners
+atsjenk@google.com
madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 0c2d5c4..ccbb9cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module freeform owners
+atsjenk@google.com
madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 318a49a..6d46a9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -310,8 +311,11 @@
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
if (mReverseDefaultRotationEnabled) {
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
- /* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
- /* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE});
+ /* fromOrientations */
+ new int[]{SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ /* toOrientations */
+ new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE});
} else {
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
/* fromOrientations */ null, /* toOrientations */ null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 867162b..24d0b99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -198,7 +198,7 @@
/**
* @return whether the given {@param aspectRatio} is valid.
*/
- private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
&& Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 09e050e..a0bd064 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1259,7 +1259,16 @@
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
mPictureInPictureParams.getAspectRatioFloat())) {
- mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(
+ params.getAspectRatioFloat())) {
+ mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
+ } else {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: New aspect ratio is not valid."
+ + " hasAspectRatio=%b"
+ + " aspectRatio=%f",
+ TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat());
+ }
}
if (mDeferredTaskInfo != null
|| PipUtils.remoteActionsChanged(params.getActions(),
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 a7171fd..82fe38c 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
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -366,11 +367,8 @@
mContext = context;
mPipDisplayLayoutState = pipDisplayLayoutState;
- boolean enablePipSizeLargeScreen = SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
-
// choose between two implementations of size spec logic
- if (enablePipSizeLargeScreen) {
+ if (supportsPipSizeLargeScreen()) {
mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
} else {
mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
@@ -515,6 +513,18 @@
}
}
+ @VisibleForTesting
+ boolean supportsPipSizeLargeScreen() {
+ // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
+ // can be injected
+ return SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
+ }
+
+ private boolean isTv() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
/** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index ccf65c2..6eb719b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -92,7 +92,6 @@
private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
private final Rect mCurrentPipBounds = new Rect();
private int mCurrentPipGravity;
- private boolean mSwitchingOrientation;
private final AccessibilityManager mA11yManager;
private final Handler mMainHandler;
@@ -176,17 +175,12 @@
}
if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) {
- mSwitchingOrientation = true;
+ // Fade out while orientation change is ongoing and fade back in once transition is
+ // finished.
mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
- .setDuration(mResizeAnimationDuration / 2)
- .withEndAction(() -> {
- mButtonLayoutManager.setOrientation(vertical
- ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
- // Only make buttons visible again in onPipTransitionFinished to keep in
- // sync with PiP content alpha animation.
- });
+ .setDuration(mResizeAnimationDuration / 2);
} else {
mButtonLayoutManager.setOrientation(vertical
? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
@@ -208,13 +202,16 @@
mEduTextDrawer.init();
}
- if (mSwitchingOrientation) {
+ mButtonLayoutManager.setOrientation(
+ mCurrentPipBounds.height() > mCurrentPipBounds.width()
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU
+ && mActionButtonsRecyclerView.getAlpha() != 1f) {
mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
}
- mSwitchingOrientation = false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index e08d40d..efc90b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -91,6 +91,16 @@
private Drawable mAppIcon;
private CharSequence mAppName;
+ private int mMenuWidth;
+ private int mMarginMenuTop;
+ private int mMarginMenuStart;
+ private int mMarginMenuSpacing;
+ private int mAppInfoPillHeight;
+ private int mWindowingPillHeight;
+ private int mMoreActionsPillHeight;
+ private int mShadowRadius;
+ private int mCornerRadius;
+
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
@@ -107,6 +117,29 @@
mSyncQueue = syncQueue;
loadAppInfo();
+ loadHandleMenuDimensions();
+ }
+
+ private void loadHandleMenuDimensions() {
+ final Resources resources = mDecorWindowContext.getResources();
+ mMenuWidth = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_width);
+ mMarginMenuTop = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_top);
+ mMarginMenuStart = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_margin_start);
+ mMarginMenuSpacing = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
+ mAppInfoPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_app_info_pill_height);
+ mWindowingPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+ mShadowRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_shadow_radius);
+ mCornerRadius = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_corner_radius);
+ mMoreActionsPillHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
}
@Override
@@ -155,6 +188,22 @@
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
+ if (mHandleMenuAppInfoPill != null) {
+ updateHandleMenuPillPositions();
+ startT.setPosition(mHandleMenuAppInfoPill.mWindowSurface,
+ mHandleMenuAppInfoPillPosition.x, mHandleMenuAppInfoPillPosition.y);
+
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+ if (shouldShowWindowingPill) {
+ startT.setPosition(mHandleMenuWindowingPill.mWindowSurface,
+ mHandleMenuWindowingPillPosition.x, mHandleMenuWindowingPillPosition.y);
+ }
+
+ startT.setPosition(mHandleMenuMoreActionsPill.mWindowSurface,
+ mHandleMenuMoreActionsPillPosition.x, mHandleMenuMoreActionsPillPosition.y);
+ }
+
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -271,64 +320,17 @@
*/
void createHandleMenu() {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- final Resources resources = mDecorWindowContext.getResources();
- final int captionWidth = mTaskInfo.getConfiguration()
- .windowConfiguration.getBounds().width();
- final int menuWidth = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_width);
- final int shadowRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_shadow_radius);
- final int cornerRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_corner_radius);
- final int marginMenuTop = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_top);
- final int marginMenuStart = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_start);
- final int marginMenuSpacing = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
- final int appInfoPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_app_info_pill_height);
- final int windowingPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height);
- final int moreActionsPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+ updateHandleMenuPillPositions();
- final int menuX, menuY;
- if (mRelayoutParams.mLayoutResId
- == R.layout.desktop_mode_app_controls_window_decor) {
- // Align the handle menu to the left of the caption.
- menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + marginMenuStart;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + marginMenuTop;
- } else {
- // Position the handle menu at the center of the caption.
- menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + marginMenuStart;
- }
-
- final int appInfoPillY = menuY;
- createAppInfoPill(t, menuX, appInfoPillY, menuWidth, appInfoPillHeight, shadowRadius,
- cornerRadius);
+ createAppInfoPill(t);
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
- final int windowingPillY = appInfoPillY + appInfoPillHeight + marginMenuSpacing;
if (shouldShowWindowingPill) {
- createWindowingPill(t, menuX, windowingPillY, menuWidth, windowingPillHeight,
- shadowRadius,
- cornerRadius);
+ createWindowingPill(t);
}
- final int moreActionsPillY;
- if (shouldShowWindowingPill) {
- // Take into account the windowing pill height and margins.
- moreActionsPillY = windowingPillY + windowingPillHeight + marginMenuSpacing;
- } else {
- // Just start after the end of the app info pill + margins.
- moreActionsPillY = appInfoPillY + appInfoPillHeight + marginMenuSpacing;
- }
- createMoreActionsPill(t, menuX, moreActionsPillY, menuWidth, moreActionsPillHeight,
- shadowRadius, cornerRadius);
+ createMoreActionsPill(t);
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
@@ -337,31 +339,31 @@
setupHandleMenu(shouldShowWindowingPill);
}
- private void createAppInfoPill(SurfaceControl.Transaction t, int x, int y, int width,
- int height, int shadowRadius, int cornerRadius) {
- mHandleMenuAppInfoPillPosition.set(x, y);
+ private void createAppInfoPill(SurfaceControl.Transaction t) {
+ final int x = (int) mHandleMenuAppInfoPillPosition.x;
+ final int y = (int) mHandleMenuAppInfoPillPosition.y;
mHandleMenuAppInfoPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
"Menu's app info pill",
- t, x, y, width, height, shadowRadius, cornerRadius);
+ t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
}
- private void createWindowingPill(SurfaceControl.Transaction t, int x, int y, int width,
- int height, int shadowRadius, int cornerRadius) {
- mHandleMenuWindowingPillPosition.set(x, y);
+ private void createWindowingPill(SurfaceControl.Transaction t) {
+ final int x = (int) mHandleMenuWindowingPillPosition.x;
+ final int y = (int) mHandleMenuWindowingPillPosition.y;
mHandleMenuWindowingPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
"Menu's windowing pill",
- t, x, y, width, height, shadowRadius, cornerRadius);
+ t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
}
- private void createMoreActionsPill(SurfaceControl.Transaction t, int x, int y, int width,
- int height, int shadowRadius, int cornerRadius) {
- mHandleMenuMoreActionsPillPosition.set(x, y);
+ private void createMoreActionsPill(SurfaceControl.Transaction t) {
+ final int x = (int) mHandleMenuMoreActionsPillPosition.x;
+ final int y = (int) mHandleMenuMoreActionsPillPosition.y;
mHandleMenuMoreActionsPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
"Menu's more actions pill",
- t, x, y, width, height, shadowRadius, cornerRadius);
+ t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
}
private void setupHandleMenu(boolean windowingPillShown) {
@@ -413,6 +415,45 @@
}
/**
+ * Updates the handle menu pills' position variables to reflect their next positions
+ */
+ private void updateHandleMenuPillPositions() {
+ final int menuX, menuY;
+ final int captionWidth = mTaskInfo.getConfiguration()
+ .windowConfiguration.getBounds().width();
+ if (mRelayoutParams.mLayoutResId
+ == R.layout.desktop_mode_app_controls_window_decor) {
+ // Align the handle menu to the left of the caption.
+ menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
+ menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+ } else {
+ // Position the handle menu at the center of the caption.
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
+ - mResult.mDecorContainerOffsetX;
+ menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+ }
+
+ // App Info pill setup.
+ final int appInfoPillY = menuY;
+ mHandleMenuAppInfoPillPosition.set(menuX, appInfoPillY);
+
+ // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+
+ final int windowingPillY, moreActionsPillY;
+ if (shouldShowWindowingPill) {
+ windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+ mHandleMenuWindowingPillPosition.set(menuX, windowingPillY);
+ moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
+ mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
+ } else {
+ // Just start after the end of the app info pill + margins.
+ moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
+ mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
+ }
+ }
+
+ /**
* Close the handle menu window
*/
void closeHandleMenu() {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 1c28c3d..64dfc3e 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -7,3 +7,4 @@
madym@google.com
hwwang@google.com
chenghsiuchang@google.com
+atsjenk@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index e986ee1..c416ad0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -137,7 +137,7 @@
portraitPosTop: Boolean
) {
assertLayers {
- this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
.then()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 416315e..18a3aa7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -18,6 +18,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
+import android.tools.common.datatypes.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -29,6 +30,7 @@
import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import org.junit.Assume
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -137,4 +139,23 @@
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
super.navBarWindowIsAlwaysVisible()
}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. Taskbar is not shown on lock screen")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. Taskbar is not shown on lock screen")
+ override fun taskBarWindowIsAlwaysVisible() {}
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] is visible at the end of the transition
+ */
+ @Postsubmit
+ @Test
+ fun taskBarLayerIsVisibleAtEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 85b2fbc..8eb41b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -34,6 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 270677470)
class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 0be08ba..4f71b83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -19,7 +19,12 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.TaskInfo;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +36,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
@@ -41,6 +47,7 @@
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ReachabilityEduLayoutTest extends ShellTestCase {
private ReachabilityEduLayout mLayout;
@@ -49,6 +56,12 @@
private View mMoveLeftButton;
private View mMoveRightButton;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+
+ @Mock
+ private TaskInfo mTaskInfo;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -69,13 +82,38 @@
}
@Test
- public void handleVisibility_activityNotLetterboxed_buttonsAreHidden() {
- mLayout.handleVisibility(/* isActivityLetterboxed */ false,
- /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */ -1,
- /* availableWidth */ 0, /* availableHeight */ 0, /* fromDoubleTap */ false);
+ public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */
+ -1, /* letterboxHorizontalPosition */ -1, /* availableWidth */
+ 0, /* availableHeight */ 0, mCompatUIConfiguration, mTaskInfo);
assertEquals(View.INVISIBLE, mMoveUpButton.getVisibility());
assertEquals(View.INVISIBLE, mMoveDownButton.getVisibility());
assertEquals(View.INVISIBLE, mMoveLeftButton.getVisibility());
assertEquals(View.INVISIBLE, mMoveRightButton.getVisibility());
}
+
+ @Test
+ public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
+ false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
+ 1, /* availableWidth */ 500, /* availableHeight */ 0, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration).setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration, never()).setUserHasSeenVerticalReachabilityEducation(
+ mTaskInfo);
+ }
+
+ @Test
+ public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
+ mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
+ true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
+ -1, /* availableWidth */ 0, /* availableHeight */ 500, mCompatUIConfiguration,
+ mTaskInfo);
+
+ verify(mCompatUIConfiguration, never())
+ .setUserHasSeenHorizontalReachabilityEducation(mTaskInfo);
+ verify(mCompatUIConfiguration).setUserHasSeenVerticalReachabilityEducation(mTaskInfo);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 359ef97..5bcc72e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -80,9 +80,8 @@
}
private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
- return new ReachabilityEduWindowManager(mContext, taskInfo,
- mSyncTransactionQueue, mCallback, mTaskListener, mDisplayLayout,
- mCompatUIConfiguration, mExecutor);
+ return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
}
private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 390c830..425bbf0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -18,7 +18,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@@ -76,7 +75,7 @@
@Mock private Resources mResources;
private PipDisplayLayoutState mPipDisplayLayoutState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private TestPipSizeSpecHandler mPipSizeSpecHandler;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -84,8 +83,6 @@
private static void setUpStaticSystemPropertiesSession() {
sStaticMockitoSession = mockitoSession()
.mockStatic(SystemProperties.class).startMocking();
- // make sure the feature flag is on
- when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
String property = invocation.getArgument(0);
if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
@@ -161,7 +158,7 @@
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
setUpStaticSystemPropertiesSession();
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
mPipSizeSpecHandler.setOverrideMinSize(null);
@@ -214,4 +211,16 @@
Assert.assertEquals(expectedSize, actualSize);
}
+
+ static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
+
+ TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
+ super(context, displayLayoutState);
+ }
+
+ @Override
+ boolean supportsPipSizeLargeScreen() {
+ return true;
+ }
+ }
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 5d79104..70c36a5 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -55,6 +55,10 @@
// GCC false-positives on this warning, and since we -Werror that's
// a problem
"-Wno-free-nonheap-object",
+
+ // Do not de-optimise cold code paths in AFDO.
+ // Some code paths might be infrequently executed but critical to latency.
+ "-fno-profile-sample-accurate",
],
include_dirs: [
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 0b58406..924fbd6 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -51,6 +51,9 @@
#include "include/gpu/GrDirectContext.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
+#ifdef __ANDROID__
+#include "renderthread/CanvasContext.h"
+#endif
namespace android {
namespace uirenderer {
@@ -489,7 +492,19 @@
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ if (paint.isAntiAlias()) {
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ } else {
+ c->save();
+#ifdef __ANDROID__
+ auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix();
+ auto transform = c->getLocalToDevice();
+ transform.postConcat(pixelSnap);
+ c->setMatrix(transform);
+#endif
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ c->restore();
+ }
}
};
struct DrawVertices final : Op {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7a12769..8394c3c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -50,6 +50,7 @@
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
+#include "effects/GainmapRenderer.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include "hwui/PaintFilter.h"
@@ -589,18 +590,25 @@
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto image = bitmap.makeImage();
+
+ if (bitmap.hasGainmap()) {
+ Paint gainmapPaint = paint ? *paint : Paint();
+ sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ SkTileMode::kClamp, SkTileMode::kClamp, gainmapPaint.sampling());
+ gainmapPaint.setShader(gainmapShader);
+ return drawRect(left, top, left + bitmap.width(), top + bitmap.height(), gainmapPaint);
+ }
+
applyLooper(paint, [&](const Paint& p) {
mCanvas->drawImage(image, left, top, p.sampling(), &p);
});
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) {
- auto image = bitmap.makeImage();
SkAutoCanvasRestore acr(mCanvas, true);
mCanvas->concat(matrix);
- applyLooper(paint, [&](const Paint& p) {
- mCanvas->drawImage(image, 0, 0, p.sampling(), &p);
- });
+ drawBitmap(bitmap, 0, 0, paint);
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -610,6 +618,16 @@
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ if (bitmap.hasGainmap()) {
+ Paint gainmapPaint = paint ? *paint : Paint();
+ sk_sp<SkShader> gainmapShader = uirenderer::MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ SkTileMode::kClamp, SkTileMode::kClamp, gainmapPaint.sampling());
+ gainmapShader = gainmapShader->makeWithLocalMatrix(SkMatrix::RectToRect(srcRect, dstRect));
+ gainmapPaint.setShader(gainmapShader);
+ return drawRect(dstLeft, dstTop, dstRight, dstBottom, gainmapPaint);
+ }
+
applyLooper(paint, [&](const Paint& p) {
mCanvas->drawImageRect(image, srcRect, dstRect, p.sampling(), &p,
SkCanvas::kFast_SrcRectConstraint);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 940d6bf..f0461be 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -53,6 +53,14 @@
bool isSurfaceReady() override;
bool isContextReady() override;
+ const SkM44& getPixelSnapMatrix() const override {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset);
+ return sSnapMatrix;
+ }
+
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
protected:
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 8ea71f1..1f92968 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -28,6 +28,7 @@
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
+#include <SkPaintFilterCanvas.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <SkRect.h>
@@ -36,15 +37,15 @@
#include <SkStream.h>
#include <SkString.h>
#include <SkTypeface.h>
-#include "include/gpu/GpuTypes.h" // from Skia
#include <android-base/properties.h>
+#include <gui/TraceUtils.h>
#include <unistd.h>
#include <sstream>
-#include <gui/TraceUtils.h>
#include "LightingInfo.h"
#include "VectorDrawable.h"
+#include "include/gpu/GpuTypes.h" // from Skia
#include "thread/CommonPool.h"
#include "tools/SkSharingProc.h"
#include "utils/Color.h"
@@ -449,6 +450,23 @@
}
}
+class ForceDitherCanvas : public SkPaintFilterCanvas {
+public:
+ ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {}
+
+protected:
+ bool onFilter(SkPaint& paint) const override {
+ paint.setDither(true);
+ return true;
+ }
+
+ void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
+ // We unroll the drawable using "this" canvas, so that draw calls contained inside will
+ // get dithering applied
+ drawable->draw(this, matrix);
+ }
+};
+
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
@@ -503,6 +521,12 @@
canvas->clear(SK_ColorTRANSPARENT);
}
+ std::optional<ForceDitherCanvas> forceDitherCanvas;
+ if (shouldForceDither()) {
+ forceDitherCanvas.emplace(canvas);
+ canvas = &forceDitherCanvas.value();
+ }
+
if (1 == nodes.size()) {
if (!nodes[0]->nothingToDraw()) {
RenderNodeDrawable root(nodes[0].get(), canvas);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index befee89..0763b06 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -98,6 +98,8 @@
bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
+ virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; }
+
private:
void renderFrameImpl(const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index c8f2e69..f22652f 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -203,6 +203,11 @@
return nullptr;
}
+bool SkiaVulkanPipeline::shouldForceDither() const {
+ if (mVkSurface && mVkSurface->isBeyond8Bit()) return false;
+ return SkiaPipeline::shouldForceDither();
+}
+
void SkiaVulkanPipeline::onContextDestroyed() {
if (mVkSurface) {
vulkanManager().destroySurface(mVkSurface);
@@ -210,6 +215,10 @@
}
}
+const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const {
+ return mVkSurface->getPixelSnapMatrix();
+}
+
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index d921ddb..01a93c7 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -55,6 +55,7 @@
bool isContextReady() override;
bool supportsExtendedRangeHdr() const override { return true; }
void setTargetSdrHdrRatio(float ratio) override;
+ const SkM44& getPixelSnapMatrix() const override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
@@ -63,6 +64,8 @@
protected:
void onContextDestroyed() override;
+ bool shouldForceDither() const override;
+
private:
renderthread::VulkanManager& vulkanManager();
renderthread::VulkanSurface* mVkSurface = nullptr;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6b2c995..1b9d41a7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -871,6 +871,10 @@
return size;
}
+const SkM44& CanvasContext::getPixelSnapMatrix() const {
+ return mRenderPipeline->getPixelSnapMatrix();
+}
+
void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3f25339..d7215de 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -200,6 +200,9 @@
SkISize getNextFrameSize() const;
+ // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center
+ const SkM44& getPixelSnapMatrix() const;
+
// Called when SurfaceStats are available.
static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
ASurfaceControlStats* stats);
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index c68fcdf..9ebad81 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -97,6 +97,7 @@
virtual bool supportsExtendedRangeHdr() const { return false; }
virtual void setTargetSdrHdrRatio(float ratio) = 0;
+ virtual const SkM44& getPixelSnapMatrix() const = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 21b6c44..2b7fa04 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -63,6 +63,18 @@
return SkMatrix::I();
}
+static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform);
+ SkMatrix invert;
+ LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert));
+ return SkM44::Translate(kOffset, kOffset)
+ .postConcat(SkM44(preRotation))
+ .preConcat(SkM44(invert));
+}
+
static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
ATRACE_CALL();
@@ -178,6 +190,8 @@
outWindowInfo->preTransform =
GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
+ outWindowInfo->pixelSnapMatrix =
+ GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform);
err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
if (err != 0 || query_value < 0) {
@@ -413,6 +427,7 @@
}
mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
+ mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform);
}
uint32_t idx;
@@ -530,6 +545,16 @@
}
}
+bool VulkanSurface::isBeyond8Bit() const {
+ switch (mWindowInfo.bufferFormat) {
+ case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+ return true;
+ default:
+ return false;
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index e2ddc6b..d3266af8 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -47,6 +47,9 @@
const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; }
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
+ const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
+
+ bool isBeyond8Bit() const;
private:
/*
@@ -105,6 +108,7 @@
SkISize actualSize;
// transform to be applied to the SkSurface to map the coordinates to the provided transform
SkMatrix preTransform;
+ SkM44 pixelSnapMatrix;
};
VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b4fbc97..b1d2e33 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -661,7 +661,10 @@
*/
public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
- /** @hide */
+ /**
+ * @hide
+ * This list contains all the flags that can be used in internal APIs for volume
+ * related operations */
@IntDef(flag = true, prefix = "FLAG", value = {
FLAG_SHOW_UI,
FLAG_ALLOW_RINGER_MODES,
@@ -681,16 +684,64 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Flags {}
- /** @hide */
+ /**
+ * @hide
+ * This list contains all the flags that can be used in SDK-visible methods for volume
+ * related operations.
+ * See for instance {@link #adjustVolume(int, int)},
+ * {@link #adjustStreamVolume(int, int, int)},
+ * {@link #adjustSuggestedStreamVolume(int, int, int)},
+ * {@link #adjustVolumeGroupVolume(int, int, int)},
+ * {@link #setStreamVolume(int, int, int)}
+ * The list contains all volume flags, but the values commented out of the list are there for
+ * maintenance reasons (for when adding flags or changing their visibility),
+ * and to document why some are not in the list (hidden or SystemApi). */
@IntDef(flag = true, prefix = "FLAG", value = {
FLAG_SHOW_UI,
FLAG_ALLOW_RINGER_MODES,
FLAG_PLAY_SOUND,
FLAG_REMOVE_SOUND_AND_VIBRATE,
FLAG_VIBRATE,
+ //FLAG_FIXED_VOLUME, removed due to @hide
+ //FLAG_BLUETOOTH_ABS_VOLUME, removed due to @SystemApi
+ //FLAG_SHOW_SILENT_HINT, removed due to @hide
+ //FLAG_HDMI_SYSTEM_AUDIO_VOLUME, removed due to @hide
+ //FLAG_ACTIVE_MEDIA_ONLY, removed due to @hide
+ //FLAG_SHOW_UI_WARNINGS, removed due to @hide
+ //FLAG_SHOW_VIBRATE_HINT, removed due to @hide
+ //FLAG_FROM_KEY, removed due to @SystemApi
+ //FLAG_ABSOLUTE_VOLUME, removed due to @hide
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PublicVolumeFlags {}
+
+ /**
+ * @hide
+ * Like PublicVolumeFlags, but for all the flags that can be used in @SystemApi methods for
+ * volume related operations.
+ * See for instance {@link #setVolumeIndexForAttributes(AudioAttributes, int, int)},
+ * {@link #setVolumeGroupVolumeIndex(int, int, int)},
+ * {@link #setStreamVolumeForUid(int, int, int, String, int, int, int)},
+ * {@link #adjustStreamVolumeForUid(int, int, int, String, int, int, int)},
+ * {@link #adjustSuggestedStreamVolumeForUid(int, int, int, String, int, int, int)}
+ * The list contains all volume flags, but the values commented out of the list are there for
+ * maintenance reasons (for when adding flags or changing their visibility),
+ * and to document which hidden values are not in the list. */
+ @IntDef(flag = true, prefix = "FLAG", value = {
+ FLAG_SHOW_UI,
+ FLAG_ALLOW_RINGER_MODES,
+ FLAG_PLAY_SOUND,
+ FLAG_REMOVE_SOUND_AND_VIBRATE,
+ FLAG_VIBRATE,
+ //FLAG_FIXED_VOLUME, removed due to @hide
FLAG_BLUETOOTH_ABS_VOLUME,
- FLAG_HDMI_SYSTEM_AUDIO_VOLUME,
+ //FLAG_SHOW_SILENT_HINT, removed due to @hide
+ //FLAG_HDMI_SYSTEM_AUDIO_VOLUME, removed due to @hide
+ //FLAG_ACTIVE_MEDIA_ONLY, removed due to @hide
+ //FLAG_SHOW_UI_WARNINGS, removed due to @hide
+ //FLAG_SHOW_VIBRATE_HINT, removed due to @hide
FLAG_FROM_KEY,
+ //FLAG_ABSOLUTE_VOLUME, removed due to @hide
})
@Retention(RetentionPolicy.SOURCE)
public @interface SystemVolumeFlags {}
@@ -981,13 +1032,13 @@
* @param direction The direction to adjust the volume. One of
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
* {@link #ADJUST_SAME}.
- * @param flags One or more flags.
+ * @param flags
* @see #adjustVolume(int, int)
* @see #setStreamVolume(int, int, int)
* @throws SecurityException if the adjustment triggers a Do Not Disturb change
* and the caller is not granted notification policy access.
*/
- public void adjustStreamVolume(int streamType, int direction, int flags) {
+ public void adjustStreamVolume(int streamType, int direction, @PublicVolumeFlags int flags) {
final IAudioService service = getService();
try {
service.adjustStreamVolumeWithAttribution(streamType, direction, flags,
@@ -1014,13 +1065,13 @@
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE},
* {@link #ADJUST_SAME}, {@link #ADJUST_MUTE},
* {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}.
- * @param flags One or more flags.
+ * @param flags
* @see #adjustSuggestedStreamVolume(int, int, int)
* @see #adjustStreamVolume(int, int, int)
* @see #setStreamVolume(int, int, int)
* @see #isVolumeFixed()
*/
- public void adjustVolume(int direction, int flags) {
+ public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
@@ -1043,13 +1094,14 @@
* @param suggestedStreamType The stream type that will be used if there
* isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is
* valid here.
- * @param flags One or more flags.
+ * @param flags
* @see #adjustVolume(int, int)
* @see #adjustStreamVolume(int, int, int)
* @see #setStreamVolume(int, int, int)
* @see #isVolumeFixed()
*/
- public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
+ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
+ @PublicVolumeFlags int flags) {
MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
@@ -1334,14 +1386,14 @@
* @param streamType The stream whose volume index should be set.
* @param index The volume index to set. See
* {@link #getStreamMaxVolume(int)} for the largest valid value.
- * @param flags One or more flags.
+ * @param flags
* @see #getStreamMaxVolume(int)
* @see #getStreamVolume(int)
* @see #isVolumeFixed()
* @throws SecurityException if the volume change triggers a Do Not Disturb change
* and the caller is not granted notification policy access.
*/
- public void setStreamVolume(int streamType, int index, int flags) {
+ public void setStreamVolume(int streamType, int index, @PublicVolumeFlags int flags) {
final IAudioService service = getService();
try {
service.setStreamVolumeWithAttribution(streamType, index, flags,
@@ -1357,7 +1409,7 @@
* @param index The volume index to set. See
* {@link #getMaxVolumeIndexForAttributes(AudioAttributes)} for the largest valid value
* {@link #getMinVolumeIndexForAttributes(AudioAttributes)} for the lowest valid value.
- * @param flags One or more flags.
+ * @param flags
* @see #getMaxVolumeIndexForAttributes(AudioAttributes)
* @see #getMinVolumeIndexForAttributes(AudioAttributes)
* @see #isVolumeFixed()
@@ -1365,7 +1417,8 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
- public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) {
+ public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index,
+ @SystemVolumeFlags int flags) {
Preconditions.checkNotNull(attr, "attr must not be null");
final IAudioService service = getService();
int groupId = getVolumeGroupIdForAttributes(attr);
@@ -1451,7 +1504,7 @@
* @param index The volume index to set. See
* {@link #getVolumeGroupMaxVolumeIndex(id)} for the largest valid value
* {@link #getVolumeGroupMinVolumeIndex(id)} for the lowest valid value.
- * @param flags One or more flags.
+ * @param flags
* @hide
*/
@SystemApi
@@ -1552,11 +1605,11 @@
* @param direction The direction to adjust the volume. One of
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
* {@link #ADJUST_SAME}.
- * @param flags One or more flags.
+ * @param flags
* @throws SecurityException if the adjustment triggers a Do Not Disturb change and the caller
* is not granted notification policy access.
*/
- public void adjustVolumeGroupVolume(int groupId, int direction, @SystemVolumeFlags int flags) {
+ public void adjustVolumeGroupVolume(int groupId, int direction, @PublicVolumeFlags int flags) {
IAudioService service = getService();
try {
service.adjustVolumeGroupVolume(groupId, direction, flags,
@@ -8234,7 +8287,7 @@
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE},
* {@link #ADJUST_SAME}, {@link #ADJUST_MUTE},
* {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}.
- * @param flags One or more flags.
+ * @param flags
* @param packageName the package name of client application
* @param uid the uid of client application
* @param pid the pid of client application
@@ -8247,7 +8300,8 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void adjustSuggestedStreamVolumeForUid(int suggestedStreamType, int direction, int flags,
+ public void adjustSuggestedStreamVolumeForUid(int suggestedStreamType, int direction,
+ @SystemVolumeFlags int flags,
@NonNull String packageName, int uid, int pid, int targetSdkVersion) {
try {
getService().adjustSuggestedStreamVolumeForUid(suggestedStreamType, direction, flags,
@@ -8277,7 +8331,7 @@
* @param direction The direction to adjust the volume. One of
* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
* {@link #ADJUST_SAME}.
- * @param flags One or more flags.
+ * @param flags
* @param packageName the package name of client application
* @param uid the uid of client application
* @param pid the pid of client application
@@ -8290,7 +8344,8 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
+ public void adjustStreamVolumeForUid(int streamType, int direction,
+ @SystemVolumeFlags int flags,
@NonNull String packageName, int uid, int pid, int targetSdkVersion) {
try {
getService().adjustStreamVolumeForUid(streamType, direction, flags, packageName, uid,
@@ -8314,7 +8369,7 @@
* @param streamType The stream whose volume index should be set.
* @param index The volume index to set. See
* {@link #getStreamMaxVolume(int)} for the largest valid value.
- * @param flags One or more flags.
+ * @param flags
* @param packageName the package name of client application
* @param uid the uid of client application
* @param pid the pid of client application
@@ -8328,7 +8383,8 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void setStreamVolumeForUid(int streamType, int index, int flags,
+ public void setStreamVolumeForUid(int streamType, int index,
+ @SystemVolumeFlags int flags,
@NonNull String packageName, int uid, int pid, int targetSdkVersion) {
try {
getService().setStreamVolumeForUid(streamType, index, flags, packageName, uid, pid,
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index f9d4efe..fe5afc5 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -268,7 +268,7 @@
boolean isVolumeControlUsingVolumeGroups();
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- oneway void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register);
+ void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register);
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
void setNotifAliasRingForTest(boolean alias);
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index 5f7d636..e3829e6 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -51,4 +51,26 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
void setLaunchCookie(in IBinder launchCookie);
+
+ /**
+ * Returns {@code true} if this token is still valid. A token is valid as long as the token
+ * hasn't timed out before it was used, and the token is only used once.
+ *
+ * <p>If the {@link IMediaProjection} is not valid, then either throws an exception if the
+ * target SDK is at least {@code U}, or returns {@code false} for target SDK below {@code U}.
+ *
+ * @throws IllegalStateException If the caller's target SDK is at least {@code U} and the
+ * projection is not valid.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ boolean isValid();
+
+ /**
+ * Sets that {@link MediaProjection#createVirtualDisplay} has been invoked with this token (it
+ * should only be called once).
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void notifyVirtualDisplayCreated(int displayId);
}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index c97265d..835e4c3 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -44,6 +44,22 @@
+ ".permission.MANAGE_MEDIA_PROJECTION)")
boolean isCurrentProjection(IMediaProjection projection);
+ /**
+ * Reshows the permisison dialog for the user to review consent they've already granted in
+ * the given projection instance.
+ *
+ * <p>Preconditions:
+ * <ul>
+ * <li>{@link IMediaProjection#isValid} returned false, rather than throwing an exception</li>
+ * <li>Given projection instance is the current projection instance.</li>
+ * <ul>
+ *
+ * <p>Returns immediately but waits to start recording until user has reviewed their consent.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void requestConsentForInvalidProjection(IMediaProjection projection);
+
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
MediaProjectionInfo getActiveProjectionInfo();
@@ -69,15 +85,18 @@
void removeCallback(IMediaProjectionWatcherCallback callback);
/**
- * Updates the content recording session. If a different session is already in progress, then
- * the pre-existing session is stopped, and the new incoming session takes over. Only updates
- * the session if the given projection is valid.
+ * Returns {@code true} if it successfully updates the content recording session. Returns
+ * {@code false} otherwise, and stops the current projection.
+ *
+ * <p>If a different session is already in progress, then the pre-existing session is stopped,
+ * and the new incoming session takes over. Only updates the session if the given projection is
+ * valid.
*
* @param incomingSession the nullable incoming content recording session
* @param projection the non-null projection the session describes
*/
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void setContentRecordingSession(in ContentRecordingSession incomingSession,
+ boolean setContentRecordingSession(in ContentRecordingSession incomingSession,
in IMediaProjection projection);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index e040bf4..f1cffb6 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -164,12 +164,21 @@
* @param handler The {@link android.os.Handler} on which the callback should be invoked, or
* null if the callback should be invoked on the calling thread's main
* {@link android.os.Looper}.
- * @throws IllegalStateException If the target SDK is
- * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and
- * up and no {@link Callback}
- * is registered. If the target SDK is less than
+ * @throws IllegalStateException In the following scenarios, if the target SDK is {@link
+ * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up:
+ * <ol>
+ * <li>If no {@link Callback} is registered.</li>
+ * <li>If {@link MediaProjectionManager#getMediaProjection}
+ * was invoked more than once to get this
+ * {@code MediaProjection} instance.
+ * <li>If this instance has already taken a recording through
+ * {@code #createVirtualDisplay}.
+ * </ol>
+ * However, if the target SDK is less than
* {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no
- * exception is thrown.
+ * exception is thrown. In case 1, recording begins even without
+ * the callback. In case 2 & 3, recording doesn't begin
+ * until the user re-grants consent in the dialog.
* @throws SecurityException If attempting to create a new virtual display associated with this
* MediaProjection instance after it has been stopped by invoking
* {@link #stop()}.
@@ -216,8 +225,13 @@
// Pass in the current session details, so they are guaranteed to only be set in
// WindowManagerService AFTER a VirtualDisplay is constructed (assuming there are no
// errors during set-up).
+ // Do not introduce a separate aidl call here to prevent a race
+ // condition between setting up the VirtualDisplay and checking token validity.
virtualDisplayConfig.setWindowManagerMirroringEnabled(true);
// Do not declare a display id to mirror; default to the default display.
+ // DisplayManagerService will ask MediaProjectionManagerService to check if the app
+ // is re-using consent. Always return the projection instance to keep this call
+ // non-blocking; no content is sent to the app until the user re-grants consent.
final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this,
virtualDisplayConfig.build(), callback, handler);
if (virtualDisplay == null) {
@@ -339,6 +353,7 @@
private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@Override
public void onStop() {
+ Slog.v(TAG, "Dispatch stop to " + mCallbacks.size() + " callbacks.");
for (CallbackRecord cbr : mCallbacks.values()) {
cbr.onStop();
}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 30fccf4..5703c42 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -231,6 +231,8 @@
if (projection == null) {
return null;
}
+ // Don't do anything here if app is re-using the token; we check how often
+ // IMediaProjection#start is invoked. Fail to the app when they start recording.
return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 031c3ff..0d9bd65 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -1169,7 +1169,7 @@
* @param flags flags containing extra action or information regarding the volume change
*/
void onVolumeChanged(@NonNull MediaSession.Token sessionToken,
- @AudioManager.Flags int flags);
+ @AudioManager.SystemVolumeFlags int flags);
/**
* Called when the default remote session is changed where the default remote session
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 9a4aa33..dea7f03 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -43,6 +43,8 @@
#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android-base/stringprintf.h>
+
#include <binder/MemoryDealer.h>
#include <cutils/compiler.h>
@@ -1276,7 +1278,8 @@
ALOGE("Could not create MediaCodec.BufferInfo.");
env->ExceptionClear();
}
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Fatal error: could not create MediaCodec.BufferInfo object");
return;
}
@@ -1309,7 +1312,8 @@
ALOGE("Could not create CodecException object.");
env->ExceptionClear();
}
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Fatal error: could not create CodecException object");
return;
}
@@ -1322,7 +1326,9 @@
CHECK(msg->findMessage("format", &format));
if (OK != ConvertMessageToMap(env, format, &obj)) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Fatal error: failed to convert format "
+ "from native to Java object");
return;
}
@@ -1353,7 +1359,8 @@
status_t err = ConvertMessageToMap(env, data, &obj);
if (err != OK) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Fatal error: failed to convert format from native to Java object");
return;
}
@@ -1374,7 +1381,8 @@
status_t err = ConvertMessageToMap(env, data, &obj);
if (err != OK) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Fatal error: failed to convert format from native to Java object");
return;
}
@@ -1385,6 +1393,18 @@
env->DeleteLocalRef(obj);
}
+std::string JMediaCodec::getExceptionMessage(const char *msg = nullptr) const {
+ if (mCodec == nullptr) {
+ return msg ?: "";
+ }
+ std::string prefix = "";
+ if (msg && msg[0] != '\0') {
+ prefix.append(msg);
+ prefix.append("\n");
+ }
+ return prefix + mCodec->getErrorLog().extract();
+}
+
void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatCallbackNotify:
@@ -1471,9 +1491,17 @@
env->Throw(exception);
}
+static std::string GetExceptionMessage(const sp<JMediaCodec> &codec, const char *msg) {
+ if (codec == NULL) {
+ return msg ?: "codec is released already";
+ }
+ return codec->getExceptionMessage(msg);
+}
+
static jint throwExceptionAsNecessary(
JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL,
- const char *msg = NULL, const sp<ICrypto>& crypto = NULL) {
+ const char *msg = NULL, const sp<ICrypto>& crypto = NULL,
+ const sp<JMediaCodec> &codec = NULL) {
switch (err) {
case OK:
return 0;
@@ -1488,23 +1516,38 @@
return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
case INVALID_OPERATION:
- jniThrowException(env, "java/lang/IllegalStateException", msg);
+ jniThrowException(
+ env, "java/lang/IllegalStateException",
+ GetExceptionMessage(codec, msg).c_str());
return 0;
case BAD_VALUE:
- jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ jniThrowException(
+ env, "java/lang/IllegalArgumentException",
+ GetExceptionMessage(codec, msg).c_str());
return 0;
default:
if (isCryptoError(err)) {
- throwCryptoException(env, err, msg, crypto);
+ throwCryptoException(
+ env, err,
+ GetExceptionMessage(codec, msg).c_str(),
+ crypto);
return 0;
}
- throwCodecException(env, err, actionCode, msg);
+ throwCodecException(
+ env, err, actionCode,
+ GetExceptionMessage(codec, msg).c_str());
return 0;
}
}
+static jint throwExceptionAsNecessary(
+ JNIEnv *env, status_t err, const sp<JMediaCodec> &codec,
+ int32_t actionCode = ACTION_CODE_FATAL) {
+ return throwExceptionAsNecessary(env, err, actionCode, NULL, NULL, codec);
+}
+
static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener(
JNIEnv *env,
jobject thiz,
@@ -1512,13 +1555,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
@@ -1528,13 +1571,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->enableOnFrameRenderedListener(enabled);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_native_setCallback(
@@ -1544,13 +1587,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->setCallback(cb);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_native_configure(
@@ -1564,7 +1607,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -1602,7 +1645,7 @@
err = codec->configure(format, bufferProducer, crypto, descrambler, flags);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_native_setSurface(
@@ -1612,7 +1655,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -1631,7 +1674,7 @@
}
status_t err = codec->setSurface(bufferProducer);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface(
@@ -1735,7 +1778,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -1749,7 +1792,7 @@
}
status_t err = codec->setInputSurface(persistentSurface);
if (err != NO_ERROR) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
}
@@ -1759,7 +1802,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -1767,7 +1810,7 @@
sp<IGraphicBufferProducer> bufferProducer;
status_t err = codec->createInputSurface(&bufferProducer);
if (err != NO_ERROR) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
return NULL;
}
@@ -1782,13 +1825,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->start();
- throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed");
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
@@ -1797,13 +1840,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->stop();
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) {
@@ -1812,7 +1855,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -1825,7 +1868,7 @@
// trigger an IllegalStateException.
err = UNKNOWN_ERROR;
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
@@ -1834,13 +1877,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->flush();
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_queueInputBuffer(
@@ -1856,7 +1899,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -1866,7 +1909,8 @@
index, offset, size, timestampUs, flags, &errorDetailMsg);
throwExceptionAsNecessary(
- env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+ env, err, ACTION_CODE_FATAL,
+ codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
}
struct NativeCryptoInfo {
@@ -1890,7 +1934,9 @@
} else if (jmode == gCryptoModes.AesCbc) {
mMode = CryptoPlugin::kMode_AES_CBC;
} else {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ base::StringPrintf("unrecognized crypto mode: %d", jmode).c_str());
return;
}
@@ -2026,7 +2072,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -2056,7 +2102,9 @@
} else if (jmode == gCryptoModes.AesCbc) {
mode = CryptoPlugin::kMode_AES_CBC;
} else {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ base::StringPrintf("Unrecognized crypto mode: %d", jmode).c_str());
return;
}
@@ -2175,8 +2223,8 @@
subSamples = NULL;
throwExceptionAsNecessary(
- env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str(),
- codec->getCrypto());
+ env, err, ACTION_CODE_FATAL,
+ codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto());
}
static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
@@ -2518,14 +2566,16 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == nullptr || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
sp<AMessage> tunings;
status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
if (err != OK) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL,
+ "error occurred while converting tunings from Java to native");
return;
}
@@ -2545,15 +2595,23 @@
}
env->MonitorExit(lock.get());
} else {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ "Failed to grab lock for a LinearBlock object");
return;
}
AString errorDetailMsg;
if (codec->hasCryptoOrDescrambler()) {
if (!memory) {
+ // It means there was an unexpected failure in extractMemoryFromContext above
ALOGI("queueLinearBlock: no ashmem memory for encrypted content");
- throwExceptionAsNecessary(env, BAD_VALUE);
+ throwExceptionAsNecessary(
+ env, BAD_VALUE, ACTION_CODE_FATAL,
+ "Unexpected error: the input buffer is not compatible with "
+ "the secure codec, and a fallback logic failed.\n"
+ "Suggestion: please try including the secure codec when calling "
+ "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
auto cryptoInfo =
@@ -2577,14 +2635,22 @@
ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err);
} else {
if (!buffer) {
+ // It means there was an unexpected failure in extractBufferFromContext above
ALOGI("queueLinearBlock: no C2Buffer found");
- throwExceptionAsNecessary(env, BAD_VALUE);
+ throwExceptionAsNecessary(
+ env, BAD_VALUE, ACTION_CODE_FATAL,
+ "Unexpected error: the input buffer is not compatible with "
+ "the non-secure codec, and a fallback logic failed.\n"
+ "Suggestion: please do not include the secure codec when calling "
+ "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
err = codec->queueBuffer(
index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
}
- throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL,
+ codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
}
static void android_media_MediaCodec_native_queueHardwareBuffer(
@@ -2595,14 +2661,16 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
sp<AMessage> tunings;
status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
if (err != OK) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL,
+ "error occurred while converting tunings from Java to native");
return;
}
@@ -2627,7 +2695,9 @@
ALOGW("Failed to wrap AHardwareBuffer into C2GraphicAllocation");
native_handle_close(handle);
native_handle_delete(handle);
- throwExceptionAsNecessary(env, BAD_VALUE);
+ throwExceptionAsNecessary(
+ env, BAD_VALUE, ACTION_CODE_FATAL,
+ "HardwareBuffer not recognized");
return;
}
std::shared_ptr<C2GraphicBlock> block = _C2BlockFactory::CreateGraphicBlock(alloc);
@@ -2636,7 +2706,9 @@
AString errorDetailMsg;
err = codec->queueBuffer(
index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
- throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL,
+ codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
}
static void android_media_MediaCodec_native_getOutputFrame(
@@ -2646,13 +2718,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->getOutputFrame(env, frame, index);
if (err != OK) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
}
@@ -2663,7 +2735,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return -1;
}
@@ -2674,7 +2746,7 @@
return (jint) index;
}
- return throwExceptionAsNecessary(env, err);
+ return throwExceptionAsNecessary(env, err, codec);
}
static jint android_media_MediaCodec_dequeueOutputBuffer(
@@ -2684,7 +2756,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return 0;
}
@@ -2696,7 +2768,7 @@
return (jint) index;
}
- return throwExceptionAsNecessary(env, err);
+ return throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_releaseOutputBuffer(
@@ -2707,13 +2779,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->releaseOutputBuffer(index, render, updatePTS, timestampNs);
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env,
@@ -2722,13 +2794,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t err = codec->signalEndOfInputStream();
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static jobject android_media_MediaCodec_getFormatNative(
@@ -2738,7 +2810,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2749,7 +2821,7 @@
return format;
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
return NULL;
}
@@ -2761,7 +2833,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2772,7 +2844,7 @@
return format;
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
return NULL;
}
@@ -2784,7 +2856,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2797,7 +2869,7 @@
// if we're out of memory, an exception was already thrown
if (err != NO_MEMORY) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
return NULL;
@@ -2810,7 +2882,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2823,7 +2895,7 @@
// if we're out of memory, an exception was already thrown
if (err != NO_MEMORY) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
return NULL;
@@ -2836,7 +2908,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2849,7 +2921,7 @@
// if we're out of memory, an exception was already thrown
if (err != NO_MEMORY) {
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
return NULL;
@@ -2862,7 +2934,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2873,7 +2945,7 @@
return name;
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
return NULL;
}
@@ -2885,7 +2957,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -2896,7 +2968,7 @@
return codecInfoObj;
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
return NULL;
}
@@ -2908,7 +2980,8 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ GetExceptionMessage(codec, NULL).c_str());
return 0;
}
@@ -2937,7 +3010,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -2948,7 +3021,7 @@
err = codec->setParameters(params);
}
- throwExceptionAsNecessary(env, err);
+ throwExceptionAsNecessary(env, err, codec);
}
static void android_media_MediaCodec_setVideoScalingMode(
@@ -2956,13 +3029,14 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
if (mode != NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW
&& mode != NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ String8::format("Unrecognized mode: %d", mode));
return;
}
@@ -2974,7 +3048,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
@@ -2986,14 +3060,14 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
jobject ret = NULL;
status_t status = codec->querySupportedVendorParameters(env, &ret);
if (status != OK) {
- throwExceptionAsNecessary(env, status);
+ throwExceptionAsNecessary(env, status, codec);
}
return ret;
@@ -3004,7 +3078,7 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return NULL;
}
@@ -3021,13 +3095,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t status = codec->subscribeToVendorParameters(env, names);
if (status != OK) {
- throwExceptionAsNecessary(env, status);
+ throwExceptionAsNecessary(env, status, codec);
}
return;
}
@@ -3037,13 +3111,13 @@
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
return;
}
status_t status = codec->unsubscribeFromVendorParameters(env, names);
if (status != OK) {
- throwExceptionAsNecessary(env, status);
+ throwExceptionAsNecessary(env, status, codec);
}
return;
}
@@ -3440,11 +3514,15 @@
if (!context->mReadonlyMapping) {
const C2BufferData data = buffer->data();
if (data.type() != C2BufferData::LINEAR) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ "Underlying buffer is not a linear buffer");
return nullptr;
}
if (data.linearBlocks().size() != 1u) {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ "Underlying buffer contains more than one block");
return nullptr;
}
C2ConstLinearBlock block = data.linearBlocks().front();
@@ -3492,7 +3570,9 @@
false, // readOnly
true /* clearBuffer */);
}
- throwExceptionAsNecessary(env, INVALID_OPERATION);
+ throwExceptionAsNecessary(
+ env, INVALID_OPERATION, ACTION_CODE_FATAL,
+ "Underlying buffer is empty");
return nullptr;
}
@@ -3515,7 +3595,9 @@
}
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
if (cstr == nullptr) {
- throwExceptionAsNecessary(env, BAD_VALUE);
+ throwExceptionAsNecessary(
+ env, BAD_VALUE, ACTION_CODE_FATAL,
+ "Error converting Java string to native");
return;
}
names->emplace_back(cstr);
@@ -3567,6 +3649,7 @@
}
status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible);
if (err != OK) {
+ // TODO: CodecErrorLog
throwExceptionAsNecessary(env, err);
}
return isCompatible;
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 616c31b..fbaf64f 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -176,6 +176,8 @@
const sp<ICrypto> &getCrypto() { return mCrypto; }
+ std::string getExceptionMessage(const char *msg) const;
+
protected:
virtual ~JMediaCodec();
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
index 3cfc0fe..4952e01 100644
--- a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -62,12 +62,10 @@
@Override
public void registerCallback(IMediaProjectionCallback callback) throws RemoteException {
-
}
@Override
public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException {
-
}
@Override
@@ -79,4 +77,14 @@
public void setLaunchCookie(IBinder launchCookie) throws RemoteException {
mLaunchCookie = launchCookie;
}
+
+ @Override
+ public boolean isValid() throws RemoteException {
+ return true;
+ }
+
+
+ @Override
+ public void notifyVirtualDisplayCreated(int displayId) throws RemoteException {
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6f5015d..24f92c0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -5,7 +5,7 @@
* 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
+ * http://www.apache.org/licenses/LICENSE-2.0N
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -74,7 +74,6 @@
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
- Log.d(Constants.LOG_TAG, "Existing activity received new intent")
try {
val viewModel: CredentialSelectorViewModel by viewModels()
val (isCancellationRequest, shouldShowCancellationUi, appDisplayName) =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index edc902e..53731f0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
@@ -38,6 +39,12 @@
initialValue = ModalBottomSheetValue.Expanded,
skipHalfExpanded = true
)
+ val sysUiController = rememberSystemUiController()
+ if (state.targetValue == ModalBottomSheetValue.Hidden) {
+ setTransparentSystemBarsColor(sysUiController)
+ } else {
+ setBottomSheetSystemBarsColor(sysUiController)
+ }
ModalBottomSheetLayout(
sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
modifier = Modifier.background(Color.Transparent),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
index dfff3d6..244b604 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -37,6 +37,7 @@
import androidx.compose.ui.platform.LocalAccessibilityManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.R
import com.android.credentialmanager.common.material.Scrim
import com.android.credentialmanager.ui.theme.Shapes
@@ -49,6 +50,8 @@
onDismiss: () -> Unit,
dismissOnTimeout: Boolean = false,
) {
+ val sysUiController = rememberSystemUiController()
+ setTransparentSystemBarsColor(sysUiController)
BoxWithConstraints {
Box(Modifier.fillMaxSize()) {
Scrim(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index ed4cc95..648d832 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,7 +46,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.common.BaseEntry
@@ -67,7 +66,6 @@
import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
-import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
import com.android.credentialmanager.logging.CreateCredentialEvent
import com.android.internal.logging.UiEventLogger.UiEventEnum
@@ -77,8 +75,6 @@
createCredentialUiState: CreateCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val sysUiController = rememberSystemUiController()
- setBottomSheetSystemBarsColor(sysUiController)
ModalBottomSheet(
sheetContent = {
// Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index c27ac94..6d7ecd7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -41,7 +41,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.common.BaseEntry
@@ -62,8 +61,6 @@
import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.Snackbar
-import com.android.credentialmanager.common.ui.setTransparentSystemBarsColor
-import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
import com.android.credentialmanager.logging.GetCredentialEvent
import com.android.internal.logging.UiEventLogger.UiEventEnum
@@ -73,9 +70,7 @@
getCredentialUiState: GetCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val sysUiController = rememberSystemUiController()
if (getCredentialUiState.currentScreenState == GetScreenState.REMOTE_ONLY) {
- setTransparentSystemBarsColor(sysUiController)
RemoteCredentialSnackBarScreen(
onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected,
onCancel = viewModel::onUserCancel,
@@ -84,7 +79,6 @@
viewModel.uiMetrics.log(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY)
} else if (getCredentialUiState.currentScreenState
== GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY) {
- setTransparentSystemBarsColor(sysUiController)
EmptyAuthEntrySnackBarScreen(
authenticationEntryList =
getCredentialUiState.providerDisplayInfo.authenticationEntryList,
@@ -95,7 +89,6 @@
viewModel.uiMetrics.log(GetCredentialEvent
.CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY)
} else {
- setBottomSheetSystemBarsColor(sysUiController)
ModalBottomSheet(
sheetContent = {
// Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
diff --git a/packages/SettingsLib/res/drawable/ic_dock_device.xml b/packages/SettingsLib/res/drawable/ic_dock_device.xml
new file mode 100644
index 0000000..96a4900
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_dock_device.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM120,720Q87,720 63.5,696.5Q40,673 40,640L60,160Q60,127 83.5,103.5Q107,80 140,80L820,80Q853,80 876.5,103.5Q900,127 900,160L920,640Q920,673 896.5,696.5Q873,720 840,720L120,720ZM120,640L840,640Q840,640 840,640Q840,640 840,640L820,160Q820,160 820,160Q820,160 820,160L140,160Q140,160 140,160Q140,160 140,160L120,640Q120,640 120,640Q120,640 120,640ZM320,880Q259,880 209.5,850Q160,820 160,765L160,720L240,720L240,760Q253,780 274.5,790Q296,800 320,800L640,800Q664,800 685.5,790.5Q707,781 720,761L720,720L800,720L800,765Q800,820 750.5,850Q701,880 640,880L320,880ZM480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index 6c0eab3..e38e041 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -55,7 +55,7 @@
new Device(
AudioDeviceInfo.TYPE_DOCK,
MediaRoute2Info.TYPE_DOCK,
- R.drawable.ic_headphone),
+ R.drawable.ic_dock_device),
new Device(
AudioDeviceInfo.TYPE_HDMI,
MediaRoute2Info.TYPE_HDMI,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7a97b78..b0a1927 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3748,7 +3748,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 216;
+ private static final int SETTINGS_VERSION = 217;
private final int mUserId;
@@ -5711,7 +5711,7 @@
.getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE);
if (currentSetting.isNull()) {
final int resourceId =
- com.android.internal.R.string.config_defaultCredentialProviderService;
+ com.android.internal.R.array.config_defaultCredentialProviderService;
final Resources resources = getContext().getResources();
// If the config has not be defined we might get an exception. We also get
// values from both the string array type and the single string in case the
@@ -5771,6 +5771,39 @@
currentVersion = 216;
}
+ if (currentVersion == 216) {
+ // Version 216: Set a default value for Credential Manager service.
+ // We are doing this migration again because of an incorrect setting.
+
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting currentSetting = secureSettings
+ .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE);
+ if (currentSetting.isNull()) {
+ final int resourceId =
+ com.android.internal.R.array.config_defaultCredentialProviderService;
+ final Resources resources = getContext().getResources();
+ // If the config has not be defined we might get an exception.
+ final List<String> providers = new ArrayList<>();
+ try {
+ providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
+ } catch (Resources.NotFoundException e) {
+ Slog.w(LOG_TAG,
+ "Get default array Cred Provider not found: " + e.toString());
+ }
+
+ if (!providers.isEmpty()) {
+ final String defaultValue = String.join(":", providers);
+ Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as CredMan Service "
+ + "for user " + userId);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.CREDENTIAL_SERVICE, defaultValue, null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 217;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index 28f25e0..bd99a8b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -34,6 +34,143 @@
new ArraySet<String>(Arrays.asList(
"app_compat_overrides",
"game_overlay",
- "namespace1"
+ "namespace1",
+ "accessibility",
+ "activity_manager",
+ "activity_manager_native_boot",
+ "adaptive_charging",
+ "adservices",
+ "aiai_controlled_releases",
+ "alarm_manager",
+ "app_cloning",
+ "app_compat",
+ "app_compat_overrides",
+ "app_hibernation",
+ "app_standby",
+ "appsearch",
+ "arc_app_compat",
+ "astrea_controlled_releases",
+ "attention_manager_service",
+ "auto_pin_confirmation",
+ "autofill",
+ "backup_and_restore",
+ "base",
+ "battery_saver",
+ "biometrics",
+ "bluetooth",
+ "bluetooth_native",
+ "camera_native",
+ "captive_portal_login",
+ "car",
+ "cellular_security",
+ "clipboard",
+ "codegen_feature_flag_extractor",
+ "companion",
+ "configuration",
+ "connectivity",
+ "connectivity_thermal_power_manager",
+ "constrain_display_apis",
+ "content_capture",
+ "credential_manager",
+ "device_idle",
+ "device_personalization_services",
+ "device_policy_manager",
+ "devicelock",
+ "display_manager",
+ "dropbox",
+ "edgetpu_native",
+ "exo",
+ "flipendo",
+ "game_driver",
+ "game_overlay",
+ "gantry",
+ "halyard_demo",
+ "haptics",
+ "hdmi_control",
+ "health_fitness",
+ "input",
+ "input_method",
+ "input_native",
+ "input_native_boot",
+ "intelligence_bubbles",
+ "interaction_jank_monitor",
+ "ipsec",
+ "jobscheduler",
+ "kiwi",
+ "latency_tracker",
+ "launcher",
+ "launcher_lily",
+ "leaked_animator",
+ "lmkd_native",
+ "location",
+ "logcat_manager",
+ "low_power_standby",
+ "media",
+ "media_better_together",
+ "media_native",
+ "memory_safety_native",
+ "memory_safety_native_boot",
+ "mglru_native",
+ "nearby",
+ "netd_native",
+ "nnapi_native",
+ "notification_assistant",
+ "odad",
+ "on_device_abuse",
+ "on_device_personalization",
+ "oslo",
+ "ota",
+ "package_manager_service",
+ "permissions",
+ "privacy",
+ "private_compute_services",
+ "profcollect_native_boot",
+ "remote_auth",
+ "remote_key_provisioning_native",
+ "rollback",
+ "rollback_boot",
+ "rotation_resolver",
+ "runtime",
+ "runtime_native",
+ "runtime_native_boot",
+ "sdk_sandbox",
+ "settings_stats",
+ "shared",
+ "shared_native",
+ "shared_native_boot",
+ "statsd_java",
+ "statsd_java_boot",
+ "statsd_native",
+ "statsd_native_boot",
+ "storage_native_boot",
+ "surface_flinger_native_boot",
+ "swcodec_native",
+ "system_scheduler",
+ "system_server_watchdog",
+ "system_time",
+ "systemui",
+ "tare",
+ "telephony",
+ "testing",
+ "tethering",
+ "text",
+ "textclassifier",
+ "touchflow_native",
+ "tv_hdr_output_control",
+ "twoshay_native",
+ "uwb",
+ "vcn",
+ "vendor_system_native",
+ "vendor_system_native_boot",
+ "virtualization_framework_native",
+ "vpn",
+ "wallpaper_content",
+ "wear",
+ "wearable_sensing",
+ "widget",
+ "wifi",
+ "window_manager",
+ "window_manager_native_boot",
+ "wrong"
));
}
diff --git a/packages/SettingsProvider/test/AndroidManifest.xml b/packages/SettingsProvider/test/AndroidManifest.xml
index ebdf9b1..6464c12 100644
--- a/packages/SettingsProvider/test/AndroidManifest.xml
+++ b/packages/SettingsProvider/test/AndroidManifest.xml
@@ -20,7 +20,8 @@
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 067efe9..e069a9a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -455,7 +455,8 @@
intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce);
intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
- context.sendBroadcast(intent, android.Manifest.permission.DUMP);
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+ android.Manifest.permission.DUMP);
}
/**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ff57052..36a0b5d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -872,6 +872,20 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".contrast.ContrastDialogActivity"
+ android:label="@string/quick_settings_contrast_label"
+ android:theme="@style/Theme.SystemUI.ContrastDialog"
+ android:finishOnCloseSystemDialogs="true"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".ForegroundServicesDialog"
android:process=":fgservices"
android:excludeFromRecents="true"
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 4df7a44..3ec3b5c 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
@@ -25,8 +25,10 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockConfig
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockFaceConfig
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockSettings
@@ -63,6 +65,8 @@
override lateinit var animations: DefaultClockAnimations
private set
+ override val config = ClockConfig(hasCustomPositionUpdatedAnimation = true)
+
init {
val parent = FrameLayout(ctx)
smallClock =
@@ -103,6 +107,8 @@
private var isRegionDark = false
protected var targetRegion: Rect? = null
+ override val config = ClockFaceConfig()
+
override var logBuffer: LogBuffer?
get() = view.logBuffer
set(value) {
@@ -254,9 +260,6 @@
override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
largeClock.moveForSplitShade(fromRect, toRect, fraction)
}
-
- override val hasCustomPositionUpdatedAnimation: Boolean
- get() = true
}
class AnimationState(
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 c279053..322fc77 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -63,6 +63,9 @@
/** A large version of the clock, appropriate when a bigger viewport is available */
val largeClock: ClockFaceController
+ /** Determines the way the hosting app should behave when rendering either clock face */
+ val config: ClockConfig
+
/** Events that clocks may need to respond to */
val events: ClockEvents
@@ -91,6 +94,9 @@
/** View that renders the clock face */
val view: View
+ /** Determines the way the hosting app should behave when rendering this clock face */
+ val config: ClockFaceConfig
+
/** Events specific to this clock face */
val events: ClockFaceEvents
@@ -109,9 +115,6 @@
/** Call whenever the locale changes */
fun onLocaleChanged(locale: Locale) {}
- val isReactiveToTone
- get() = true
-
/** Call whenever the color palette should update */
fun onColorPaletteChanged(resources: Resources) {}
@@ -144,14 +147,6 @@
* 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
*/
fun onPickerCarouselSwiping(swipingFraction: Float, previewRatio: Float) {}
-
- /**
- * Whether this clock has a custom position update animation. If true, the keyguard will call
- * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
- * animation will be used (e.g. a simple translation).
- */
- val hasCustomPositionUpdatedAnimation
- get() = false
}
/** Events that have specific data about the related face */
@@ -159,14 +154,6 @@
/** 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
-
- /** Call to check whether the clock consumes weather data */
- val hasCustomWeatherDataDisplay: Boolean
- get() = false
-
/**
* Region Darkness specific to the clock face.
* - isRegionDark = dark theme -> clock should be light
@@ -203,6 +190,28 @@
val name: String,
)
+/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
+data class ClockConfig(
+ /**
+ * Whether this clock has a custom position update animation. If true, the keyguard will call
+ * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+ * animation will be used (e.g. a simple translation).
+ */
+ val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+ /** True if the clock will react to tone changes in the seed color. */
+ val isReactiveToTone: Boolean = true,
+)
+
+/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
+data class ClockFaceConfig(
+ /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+ val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
+
+ /** Call to check whether the clock consumes weather data */
+ val hasCustomWeatherDataDisplay: Boolean = false,
+)
+
/** Structure for keeping clock-specific settings */
@Keep
data class ClockSettings(
diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
new file mode 100644
index 0000000..4181220
--- /dev/null
+++ b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <item android:state_selected="true">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimary"
+ android:width="@dimen/contrast_dialog_button_stroke_width" />
+ <corners android:radius="@dimen/contrast_dialog_button_radius"/>
+ </shape>
+ </item>
+
+ <item>
+ <layer-list>
+ <item android:top="@dimen/contrast_dialog_button_stroke_width"
+ android:bottom="@dimen/contrast_dialog_button_stroke_width"
+ android:left="@dimen/contrast_dialog_button_stroke_width"
+ android:right="@dimen/contrast_dialog_button_stroke_width">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
+ <corners android:radius="@dimen/contrast_dialog_button_radius"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+</selector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml
new file mode 100644
index 0000000..aa5b5ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_contrast_high.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 android:autoMirrored="true" android:height="20dp"
+ android:viewportHeight="20" android:viewportWidth="66"
+ android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#F2F1E8"
+ android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z"
+ android:strokeColor="#1B1C17" android:strokeWidth="1"/>
+ <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
+ <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
+ <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
new file mode 100644
index 0000000..89519b8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector android:autoMirrored="true" android:height="20dp"
+ android:viewportHeight="20" android:viewportWidth="66"
+ android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
+ <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
+ <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
+ <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
new file mode 100644
index 0000000..f914975
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector android:autoMirrored="true" android:height="20dp"
+ android:viewportHeight="20" android:viewportWidth="66"
+ android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
+ <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
+ <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
+ <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml
new file mode 100644
index 0000000..8e885cf
--- /dev/null
+++ b/packages/SystemUI/res/layout/contrast_dialog.xml
@@ -0,0 +1,127 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_standard"
+ android:layout_width="@dimen/contrast_dialog_button_total_size"
+ android:layout_height="@dimen/contrast_dialog_button_total_size"
+ android:background="@drawable/contrast_dialog_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_contrast_standard"/>
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_dialog_button_text_size"
+ android:text="@string/quick_settings_contrast_standard"
+ android:textColor="?androidprv:attr/textColorPrimary"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
+ android:layout_height="match_parent" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_medium"
+ android:layout_width="@dimen/contrast_dialog_button_total_size"
+ android:layout_height="@dimen/contrast_dialog_button_total_size"
+ android:background="@drawable/contrast_dialog_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_contrast_medium"/>
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_dialog_button_text_size"
+ android:text="@string/quick_settings_contrast_medium"
+ android:textColor="?androidprv:attr/textColorPrimary"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
+ android:layout_height="match_parent" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_high"
+ android:layout_width="@dimen/contrast_dialog_button_total_size"
+ android:layout_height="@dimen/contrast_dialog_button_total_size"
+ android:background="@drawable/contrast_dialog_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_contrast_high"/>
+
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_dialog_button_text_size"
+ android:text="@string/quick_settings_contrast_high"
+ android:textColor="?androidprv:attr/textColorPrimary"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2663ffb..714d495 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1735,6 +1735,15 @@
<dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
<dimen name="broadcast_dialog_margin">16dp</dimen>
+ <!-- Contrast dialog -->
+ <dimen name="contrast_dialog_button_total_size">90dp</dimen>
+ <dimen name="contrast_dialog_button_inner_size">82dp</dimen>
+ <dimen name="contrast_dialog_button_radius">20dp</dimen>
+ <dimen name="contrast_dialog_button_stroke_width">4dp</dimen>
+ <dimen name="contrast_dialog_button_text_size">14sp</dimen>
+ <dimen name="contrast_dialog_button_text_spacing">4dp</dimen>
+ <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen>
+
<!-- Shadow for dream overlay clock complication -->
<dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
<dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1dd12ee..f1777f8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -757,6 +757,15 @@
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
+ <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_contrast_label">Contrast</string>
+ <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_contrast_standard">Standard</string>
+ <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_contrast_medium">Medium</string>
+ <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_contrast_high">High</string>
+
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
<!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8a86fd5..064cea1 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -377,6 +377,10 @@
<item name="android:windowBackground">@android:color/transparent</item>
</style>
+ <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
<style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
<item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item>
</style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4d7d0ea..3c447a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@
public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
// The notification panel expansion fraction is > 0
public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+ // When keyguard will be dismissed but didn't start animation yet
+ public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -172,6 +174,7 @@
SYSUI_STATE_AWAKE,
SYSUI_STATE_WAKEFULNESS_TRANSITION,
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
})
public @interface SystemUiStateFlags {}
@@ -270,6 +273,9 @@
if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) {
str.add("notif_visible");
}
+ if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
+ str.add("keygrd_going_away");
+ }
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 3b9060a..0779653 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -46,10 +46,10 @@
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockTickRate
+import com.android.systemui.plugins.WeatherData
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.plugins.WeatherData
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -144,8 +144,10 @@
val currentViewRect = Rect(left, top, right, bottom)
val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
- if (currentViewRect.width() != oldViewRect.width() ||
- currentViewRect.height() != oldViewRect.height()) {
+ if (
+ currentViewRect.width() != oldViewRect.width() ||
+ currentViewRect.height() != oldViewRect.height()
+ ) {
updateRegionSampler(view)
}
}
@@ -425,7 +427,7 @@
}
isRunning = true
- when (clockFace.events.tickRate) {
+ when (clockFace.config.tickRate) {
ClockTickRate.PER_MINUTE -> {
/* Handled by KeyguardClockSwitchController */
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 07333f7..9290220 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,7 +40,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.log.dagger.KeyguardClockLog;
-import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.log.LogLevel;
@@ -469,7 +468,7 @@
}
@Nullable
- private ClockController getClock() {
+ public ClockController getClock() {
return mClockEventController.getClock();
}
@@ -535,13 +534,6 @@
}
return ((mCurrentClockSize == LARGE) ? clock.getLargeClock() : clock.getSmallClock())
- .getEvents().getHasCustomWeatherDataDisplay();
- }
-
- /** Gets the animations for the current clock. */
- @Nullable
- public ClockAnimations getClockAnimations() {
- ClockController clock = getClock();
- return clock == null ? null : clock.getAnimations();
+ .getConfig().getHasCustomWeatherDataDisplay();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index fd55d69..c4df836 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,12 +16,13 @@
package com.android.keyguard;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Slog;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.plugins.ClockAnimations;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -241,8 +242,9 @@
}
}
- /** Gets the animations for the current clock. */
- public ClockAnimations getClockAnimations() {
- return mKeyguardClockSwitchController.getClockAnimations();
+ /** Gets the current clock controller. */
+ @Nullable
+ public ClockController getClockController() {
+ return mKeyguardClockSwitchController.getClock();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 6a6e81e..14810d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -30,7 +30,6 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -50,7 +49,7 @@
android.R.attr.textColorPrimary).getDefaultColor();
private int mPosition = 0;
private final PinShapeAdapter mPinShapeAdapter;
- private Animation mCurrentPlayingAnimation;
+ private ValueAnimator mValueAnimator = ValueAnimator.ofFloat(1f, 0f);
public PinShapeNonHintingView(Context context, AttributeSet attrs) {
super(context, attrs);
mPinShapeAdapter = new PinShapeAdapter(context);
@@ -80,15 +79,17 @@
Log.e(getClass().getName(), "Trying to delete a non-existent char");
return;
}
+ if (mValueAnimator.isRunning()) {
+ mValueAnimator.end();
+ }
mPosition--;
ImageView pinDot = (ImageView) getChildAt(mPosition);
- ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
- animator.addUpdateListener(valueAnimator -> {
+ mValueAnimator.addUpdateListener(valueAnimator -> {
float value = (float) valueAnimator.getAnimatedValue();
pinDot.setScaleX(value);
pinDot.setScaleY(value);
});
- animator.addListener(new AnimatorListenerAdapter() {
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
@@ -96,11 +97,10 @@
PinShapeNonHintingView.this,
new PinShapeViewTransition());
removeView(pinDot);
- mCurrentPlayingAnimation = null;
}
});
- animator.setDuration(PasswordTextView.DISAPPEAR_DURATION);
- animator.start();
+ mValueAnimator.setDuration(PasswordTextView.DISAPPEAR_DURATION);
+ mValueAnimator.start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 12b5705..f3c71da 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -210,8 +210,8 @@
// Saving in instance variable since to prevent GC since
// NotificationShadeWindowController.registerCallback() only keeps weak references.
mNotificationShadeCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded,
- isDreaming) ->
+ (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
+ panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 868ffcf..e0b9f01 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -34,6 +34,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
@@ -43,7 +44,6 @@
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -52,7 +52,7 @@
import javax.inject.Inject
import javax.inject.Provider
-/***
+/**
* Controls two ripple effects:
* 1. Unlocked ripple: shows when authentication is successful
* 2. UDFPS dwell ripple: shows when the user has their finger down on the UDFPS area and reacts
@@ -71,14 +71,15 @@
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val commandRegistry: CommandRegistry,
private val notificationShadeWindowController: NotificationShadeWindowController,
- private val bypassController: KeyguardBypassController,
- private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
private val featureFlags: FeatureFlags,
private val logger: KeyguardLogger,
- rippleView: AuthRippleView?
-) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
+ private val biometricUnlockController: BiometricUnlockController,
+ rippleView: AuthRippleView?
+) :
+ ViewController<AuthRippleView>(rippleView),
+ KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@VisibleForTesting
@@ -102,8 +103,24 @@
keyguardStateController.addCallback(this)
wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
+ biometricUnlockController.addListener(biometricModeListener)
}
+ private val biometricModeListener =
+ object : BiometricUnlockController.BiometricUnlockEventsListener {
+ override fun onBiometricUnlockedWithKeyguardDismissal(
+ biometricSourceType: BiometricSourceType?
+ ) {
+ if (biometricSourceType != null) {
+ showUnlockRipple(biometricSourceType)
+ } else {
+ logger.log(TAG,
+ LogLevel.ERROR,
+ "Unexpected scenario where biometricSourceType is null")
+ }
+ }
+ }
+
@VisibleForTesting
public override fun onViewDetached() {
udfpsController?.removeCallback(udfpsControllerCallback)
@@ -113,6 +130,7 @@
keyguardStateController.removeCallback(this)
wakefulnessLifecycle.removeObserver(this)
commandRegistry.unregisterCommand("auth-ripple")
+ biometricUnlockController.removeListener(biometricModeListener)
notificationShadeWindowController.setForcePluginOpen(false, this)
}
@@ -143,9 +161,6 @@
showUnlockedRipple()
}
} else if (biometricSourceType == BiometricSourceType.FACE) {
- if (!bypassController.canBypass() && !authController.isUdfpsFingerDown) {
- return
- }
faceSensorLocation?.let {
mView.setSensorLocation(it)
circleReveal = CircleReveal(
@@ -267,7 +282,6 @@
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
mView.fadeDwellRipple()
}
- showUnlockRipple(biometricSourceType)
}
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 178cda4..ba8e60a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -306,7 +306,7 @@
lp.width = mSensorBounds.width();
lp.height = mSensorBounds.height();
RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds));
- lp.setMargins(
+ lp.setMarginsRelative(
(int) relativeToView.left,
(int) relativeToView.top,
(int) relativeToView.right,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 000213f..86940ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -38,7 +38,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -147,11 +146,6 @@
override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
return applicationScope.launch {
- combine(isAod, isAwake) { doze, awake -> doze to awake }
- .onEach { (aod, awake) -> listener.onDozeChanged(aod, awake) }
- .catch { t -> Log.w(TAG, "failed to notify new doze state", t) }
- .launchIn(this)
-
foldState
.onEach { state -> listener.onFoldChanged(state) }
.catch { t -> Log.w(TAG, "failed to notify new fold state", t) }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index edda8752..63b4288 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,7 +21,6 @@
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;
@@ -36,7 +35,6 @@
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;
@@ -59,7 +57,6 @@
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
- private final FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -68,13 +65,11 @@
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
- FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
}
@@ -113,11 +108,7 @@
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
- mClipboardOverlay.setClipData(clipData, clipSource);
- } else {
- mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
- }
+ mClipboardOverlay.setClipData(clipData, clipSource);
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
@@ -160,8 +151,6 @@
}
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/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index c312f69..5230159 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -24,12 +24,14 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
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 android.animation.Animator;
@@ -37,20 +39,14 @@
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Size;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
@@ -69,7 +65,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
-import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -167,9 +162,7 @@
@Override
public void onMinimizedViewTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
- animateFromMinimized();
- }
+ animateFromMinimized();
}
};
@@ -252,11 +245,9 @@
@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);
- }
+ if (shouldShowMinimized(insets) && !mIsMinimized) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
}
}
@@ -274,9 +265,11 @@
reset();
mClipboardLogger.setClipSource(mClipboardModel.getSource());
if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
mIsMinimized = true;
mView.setMinimized(true);
} else {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
setExpandedView();
}
animateIn();
@@ -359,7 +352,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mIsMinimized = false;
+ if (mIsMinimized) {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
+ mIsMinimized = false;
+ }
setExpandedView();
animateIn();
}
@@ -393,61 +389,6 @@
});
}
- @Override // ClipboardListener.ClipboardOverlay
- public void setClipDataLegacy(ClipData clipData, String clipSource) {
- if (mExitAnimator != null && mExitAnimator.isRunning()) {
- mExitAnimator.cancel();
- }
- reset();
- mClipboardLogger.setClipSource(clipSource);
- String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-
- boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
- && clipData.getDescription().getExtras()
- .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
- boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)
- && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource);
- if (clipData == null || clipData.getItemCount() == 0) {
- mView.showDefaultTextPreview();
- } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
- ClipData.Item item = clipData.getItemAt(0);
- if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
- if (item.getTextLinks() != null) {
- classifyText(clipData.getItemAt(0), clipSource);
- }
- }
- if (isSensitive) {
- showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
- } else {
- showEditableText(item.getText(), false);
- }
- mOnShareTapped = () -> shareContent(clipData);
- mView.showShareChip();
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
- } else if (clipData.getItemAt(0).getUri() != null) {
- if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
- }
- mOnShareTapped = () -> shareContent(clipData);
- mView.showShareChip();
- } else {
- mView.showDefaultTextPreview();
- }
- if (!isRemote) {
- maybeShowRemoteCopy(clipData);
- }
- animateIn();
- mView.announceForAccessibility(accessibilityAnnouncement);
- if (isRemote) {
- mTimeoutHandler.cancelTimeout();
- mOnUiUpdate = null;
- } else {
- mOnUiUpdate = mTimeoutHandler::resetTimeout;
- mOnUiUpdate.run();
- }
- }
-
private void maybeShowRemoteCopy(ClipData clipData) {
Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
// Only show remote copy if it's available.
@@ -470,22 +411,6 @@
mOnSessionCompleteListener = runnable;
}
- private void classifyText(ClipData.Item item, String source) {
- mBgExecutor.execute(() -> {
- Optional<RemoteAction> action = mClipboardUtils.getAction(item, source);
- mView.post(() -> {
- mView.resetActionChips();
- action.ifPresent(remoteAction -> {
- mView.setActionChip(remoteAction, () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
- });
- mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
- });
- });
- });
- }
-
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
@@ -526,43 +451,6 @@
animateOut();
}
- private void showEditableText(CharSequence text, boolean hidden) {
- mView.showTextPreview(text.toString(), hidden);
- mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = this::editText;
- }
-
- private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
- Runnable listener = () -> editImage(uri);
- ContentResolver resolver = mContext.getContentResolver();
- String mimeType = resolver.getType(uri);
- boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
- if (isSensitive) {
- mView.showImagePreview(null);
- if (isEditableImage) {
- mOnPreviewTapped = listener;
- mView.setEditAccessibilityAction(true);
- }
- } else if (isEditableImage) { // if the MIMEtype is image, try to load
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- mView.showImagePreview(thumbnail);
- mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = listener;
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- mView.showDefaultTextPreview();
- isEditableImage = false;
- }
- } else {
- mView.showDefaultTextPreview();
- }
- return isEditableImage;
- }
-
private void animateIn() {
if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
index 4b5f876..e26acd5 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
@@ -44,6 +44,12 @@
CLIPBOARD_OVERLAY_TAP_OUTSIDE(1077),
@UiEvent(doc = "clipboard overlay dismissed, miscellaneous reason")
CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078),
+ @UiEvent(doc = "clipboard overlay shown in expanded form")
+ CLIPBOARD_OVERLAY_SHOWN_EXPANDED(1356),
+ @UiEvent(doc = "clipboard overlay shown in minimized form")
+ CLIPBOARD_OVERLAY_SHOWN_MINIMIZED(1357),
+ @UiEvent(doc = "clipboard overlay expanded")
+ CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED(1358),
@UiEvent(doc = "clipboard toast shown")
CLIPBOARD_TOAST_SHOWN(1270);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index 25caaea..758a656 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -88,25 +88,4 @@
}
return actions;
}
-
- public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
- return getActions(item).stream().filter(remoteAction -> {
- ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
- return component != null && !TextUtils.equals(source, component.getPackageName());
- }).findFirst();
- }
-
- private ArrayList<RemoteAction> getActions(ClipData.Item item) {
- ArrayList<RemoteAction> actions = new ArrayList<>();
- for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
- // skip classification for incidental entities
- if (link.getEnd() - link.getStart()
- >= item.getText().length() * MINIMUM_ENTITY_PROPORTION) {
- TextClassification classification = mTextClassifier.classifyText(
- item.getText(), link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
- }
- }
- return actions;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
new file mode 100644
index 0000000..9e15c7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.contrast
+
+import android.app.UiModeManager
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
+import android.app.UiModeManager.ContrastUtils.fromContrastLevel
+import android.app.UiModeManager.ContrastUtils.toContrastLevel
+import android.content.Context
+import android.os.Bundle
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.Executor
+
+/** Dialog to select contrast options */
+class ContrastDialog(
+ context: Context?,
+ @Main private val mainExecutor: Executor,
+ private val uiModeManager: UiModeManager,
+ private val userTracker: UserTracker,
+ private val secureSettings: SecureSettings,
+) : SystemUIDialog(context), UiModeManager.ContrastChangeListener {
+
+ @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
+ lateinit var dialogView: View
+ @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ dialogView = LayoutInflater.from(context).inflate(R.layout.contrast_dialog, null)
+ setView(dialogView)
+
+ setTitle(R.string.quick_settings_contrast_label)
+ setNeutralButton(R.string.cancel) { _, _ ->
+ secureSettings.putFloatForUser(
+ Settings.Secure.CONTRAST_LEVEL,
+ initialContrast,
+ userTracker.userId
+ )
+ dismiss()
+ }
+ setPositiveButton(R.string.done) { _, _ -> dismiss() }
+ super.onCreate(savedInstanceState)
+
+ contrastButtons =
+ mapOf(
+ CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
+ CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
+ CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+ )
+
+ contrastButtons.forEach { (contrastLevel, contrastButton) ->
+ contrastButton.setOnClickListener {
+ val contrastValue = fromContrastLevel(contrastLevel)
+ secureSettings.putFloatForUser(
+ Settings.Secure.CONTRAST_LEVEL,
+ contrastValue,
+ userTracker.userId
+ )
+ }
+ }
+
+ initialContrast = uiModeManager.contrast
+ highlightContrast(toContrastLevel(initialContrast))
+ }
+
+ override fun onStart() {
+ super.onStart()
+ uiModeManager.addContrastChangeListener(mainExecutor, this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ uiModeManager.removeContrastChangeListener(this)
+ }
+
+ override fun onContrastChanged(contrast: Float) {
+ highlightContrast(toContrastLevel(contrast))
+ }
+
+ private fun highlightContrast(contrast: Int) {
+ contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
new file mode 100644
index 0000000..70d7138
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.contrast
+
+import android.app.Activity
+import android.app.UiModeManager
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Trampoline activity responsible for creating a [ContrastDialog] */
+class ContrastDialogActivity
+@Inject
+constructor(
+ private val context: Context,
+ @Main private val mainExecutor: Executor,
+ private val uiModeManager: UiModeManager,
+ private val userTracker: UserTracker,
+ private val secureSettings: SecureSettings
+) : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val contrastDialog =
+ ContrastDialog(context, mainExecutor, uiModeManager, userTracker, secureSettings)
+ contrastDialog.show()
+ finish()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 3cf26b3..dba353b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.contrast.ContrastDialogActivity;
import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
@@ -73,6 +74,12 @@
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+ /** Inject into ContrastDialogActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(ContrastDialogActivity.class)
+ public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity);
+
/** Inject into UsbDebuggingActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 2ea7bce..570132e1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -303,10 +303,6 @@
}
flingToExpansion(verticalVelocity, expansion);
-
- if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
- mCurrentScrimController.reset();
- }
break;
default:
mVelocityTracker.addMovement(motionEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4d6aef5..0913cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -219,7 +219,7 @@
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/272091103): Tracking Bug
@JvmField
- val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
+ val ASYNC_INFLATE_BOUNCER = releasedFlag(229, "async_inflate_bouncer")
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/273341787): Tracking Bug
@@ -285,7 +285,7 @@
/** Enables Font Scaling Quick Settings tile */
// TODO(b/269341316): Tracking Bug
@JvmField
- val ENABLE_FONT_SCALING_TILE = unreleasedFlag(509, "enable_font_scaling_tile", teamfood = true)
+ val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile")
/** Enables new QS Edit Mode visual refresh */
// TODO(b/269787742): Tracking Bug
@@ -417,7 +417,7 @@
// TODO(b/273509374): Tracking Bug
@JvmField
- val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = unreleasedFlag(1006,
+ val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006,
"always_show_home_controls_on_dreams")
// 1100 - windowing
@@ -513,6 +513,11 @@
sysPropBooleanFlag(
1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true)
+ // TODO(b/273443374): Tracking Bug
+ @Keep
+ @JvmField val LOCKSCREEN_LIVE_WALLPAPER =
+ sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
@@ -575,9 +580,6 @@
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
- // TODO(b/268484562): Tracking bug
- @JvmField val SCREENSHOT_METADATA_REFACTOR = releasedFlag(1305, "screenshot_metadata_refactor")
-
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -608,8 +610,6 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
- // TODO(b/267162944): Tracking bug
- @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index a173f8b..2ef5e19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -90,9 +90,9 @@
private fun updateResources() {
context.resources.apply {
- filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
- emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
- backgroundColor = getColor(R.color.backlight_indicator_background)
+ filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
+ emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
+ backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
rootProperties =
RootProperties(
cornerRadius =
@@ -224,7 +224,6 @@
private fun setWindowTitle() {
val attrs = window.attributes
- // TODO(b/271796169): check if title needs to be a translatable resource.
attrs.title = "KeyboardBacklightDialog"
attrs?.y = dialogBottomMargin
window.attributes = attrs
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b1efdd7..c102c5b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2856,14 +2856,14 @@
+ " wasShowing=" + wasShowing);
}
+ mKeyguardUnlockAnimationControllerLazy.get()
+ .notifyFinishedKeyguardExitAnimation(cancelled);
finishSurfaceBehindRemoteAnimation(cancelled);
// Dispatch the callback on animation finishes.
mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
});
- mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
- cancelled);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 76f20d25..3567d81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -25,6 +25,7 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -43,12 +44,14 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -195,6 +198,7 @@
private val dozeParameters: DozeParameters,
private val authController: AuthController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
+ @Main private val mainDispatcher: CoroutineDispatcher
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -387,6 +391,7 @@
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ .flowOn(mainDispatcher)
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
@@ -466,7 +471,7 @@
}
val callback =
- object : BiometricUnlockController.BiometricModeListener {
+ object : BiometricUnlockController.BiometricUnlockEventsListener {
override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
dispatchUpdate()
}
@@ -476,10 +481,10 @@
}
}
- biometricUnlockController.addBiometricModeListener(callback)
+ biometricUnlockController.addListener(callback)
dispatchUpdate()
- awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ awaitClose { biometricUnlockController.removeListener(callback) }
}
override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 9212aa1..970d004 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -142,22 +142,18 @@
(isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
needsFullscreenBouncer()
- if (!resumeBouncer && isBouncerShowing()) {
- // If bouncer is visible, the bouncer is already showing.
- return
- }
-
Trace.beginSection("KeyguardBouncer#show")
repository.setPrimaryScrimmed(isScrimmed)
if (isScrimmed) {
setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
}
+ // In this special case, we want to hide the bouncer and show it again. We want to emit
+ // show(true) again so that we can reinflate the new view.
if (resumeBouncer) {
- primaryBouncerView.delegate?.resume()
- // Bouncer is showing the next security screen and we just need to prompt a resume.
- return
+ repository.setPrimaryShow(false)
}
+
if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
// Keyguard is done.
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 172f922..72dc7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -113,6 +113,7 @@
view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
if (isShowing) {
// Reset Security Container entirely.
+ view.visibility = View.VISIBLE
securityContainerController.reinflateViewFlipper {
// Reset Security Container entirely.
securityContainerController.onBouncerVisibilityChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 4f5bbb7..81de607 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -239,7 +239,7 @@
if (view.getId() == R.id.log_access_dialog_allow_button) {
mCallback.approveAccessForClient(mUid, mPackageName);
finish();
- } else if (view.getId() == R.id.log_access_dialog_allow_button) {
+ } else if (view.getId() == R.id.log_access_dialog_deny_button) {
declineLogAccess();
finish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c4e76b2..ccddd1d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -60,7 +60,7 @@
import dagger.Lazy;
public class MediaProjectionPermissionActivity extends Activity
- implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ implements DialogInterface.OnClickListener {
private static final String TAG = "MediaProjectionPermissionActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
private static final String ELLIPSIS = "\u2026";
@@ -215,7 +215,8 @@
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setDialogSize(dialog);
- dialog.setOnCancelListener(this);
+ dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
+ dialog.setOnDismissListener(this::onDialogDismissedOrCancelled);
dialog.create();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
@@ -283,9 +284,10 @@
return intent;
}
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
+ private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
+ if (!isFinishing()) {
+ finish();
+ }
}
private boolean isPartialScreenSharingEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 8f1c904..30ee147 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -63,6 +63,10 @@
override fun onStateChanged(newState: Int) {
refreshMediaPosition()
}
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ refreshMediaPosition()
+ }
}
)
configurationController.addCallback(
@@ -198,7 +202,8 @@
mediaHost.visible &&
!bypassController.bypassEnabled &&
keyguardOrUserSwitcher &&
- allowMediaPlayerOnLockScreen
+ allowMediaPlayerOnLockScreen &&
+ shouldBeVisibleForSplitShade()
if (visible) {
showMediaPlayer()
} else {
@@ -206,6 +211,19 @@
}
}
+ private fun shouldBeVisibleForSplitShade(): Boolean {
+ if (!useSplitShade) {
+ return true
+ }
+ // We have to explicitly hide media for split shade when on AOD, as it is a child view of
+ // keyguard status view, and nothing hides keyguard status view on AOD.
+ // When using the double-line clock, it is not an issue, as media gets implicitly hidden
+ // by the clock. This is not the case for single-line clock though.
+ // For single shade, we don't need to do it, because media is a child of NSSL, which already
+ // gets hidden on AOD.
+ return !statusBarStateController.isDozing
+ }
+
private fun showMediaPlayer() {
if (useSplitShade) {
setVisibility(splitShadeContainer, View.VISIBLE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 9928c4f..f50a7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -205,7 +205,8 @@
&& mController.isSubStatusSupported()
&& mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
boolean isActiveWithOngoingSession =
- (device.hasOngoingSession() && currentlyConnected);
+ (device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
+ mController.getSelectedMediaDevice(), device)));
boolean isHost = device.isHostForOngoingSession()
&& isActiveWithOngoingSession;
if (isHost) {
@@ -224,10 +225,17 @@
if (isActiveWithOngoingSession) {
//Selected device which has ongoing session, disable seekbar since we
//only allow volume control on Host
- initSeekbar(device, isCurrentSeekbarInvisible);
mCurrentActivePosition = position;
}
- setUpDeviceIcon(device);
+ boolean showSeekbar =
+ (!device.hasOngoingSession() && currentlyConnected);
+ if (showSeekbar) {
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ initSeekbar(device, isCurrentSeekbarInvisible);
+ } else {
+ setUpDeviceIcon(device);
+ }
mSubTitleText.setText(device.getSubtextString());
Drawable deviceStatusIcon =
device.hasOngoingSession() ? mContext.getDrawable(
@@ -241,8 +249,8 @@
updateTwoLineLayoutContentAlpha(
updateClickActionBasedOnSelectionBehavior(device)
? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
- setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */,
- isActiveWithOngoingSession /* showSeekBar */,
+ setTwoLineLayout(device, currentlyConnected /* bFocused */,
+ showSeekbar /* showSeekBar */,
false /* showProgressBar */, true /* showSubtitle */,
deviceStatusIcon != null /* showStatus */,
isActiveWithOngoingSession /* isFakeActive */);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 731bb2f..73ab5272 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -270,10 +270,10 @@
final Drawable backgroundDrawable;
if (mController.isAdvancedLayoutSupported() && mController.isSubStatusSupported()) {
backgroundDrawable = mContext.getDrawable(
- showSeekBar ? R.drawable.media_output_item_background_active
+ showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active
: R.drawable.media_output_item_background).mutate();
backgroundDrawable.setTint(
- showSeekBar ? mController.getColorConnectedItemBackground()
+ showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground()
: mController.getColorItemBackground());
mIconAreaLayout.setBackgroundTintList(
ColorStateList.valueOf(showProgressBar || isFakeActive
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index 70040c7..c804df8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -363,12 +363,8 @@
}
fun popOffEdge(startingVelocity: Float) {
- val heightStretchAmount = startingVelocity * 50
- val widthStretchAmount = startingVelocity * 150
- val scaleStretchAmount = startingVelocity * 0.8f
- backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount)
- backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount)
- scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount)
+ scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity * -.8f)
+ horizontalTranslation.stretchTo(stretchAmount = 0f, startingVelocity * 200f)
}
fun popScale(startingVelocity: Float) {
@@ -410,7 +406,7 @@
arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate)
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
- scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
+ scalePivotX.updateRestingPosition(restingParams.scalePivotX, animate)
backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
backgroundEdgeCornerRadius.updateRestingPosition(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index a29eb3b..3770b28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -49,6 +49,7 @@
private const val TAG = "BackPanelController"
private const val ENABLE_FAILSAFE = true
+private const val FAILSAFE_DELAY_MS = 350L
private const val PX_PER_SEC = 1000
private const val PX_PER_MS = 1
@@ -64,9 +65,9 @@
private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L
private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L
-private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 50L
-private const val POP_ON_FLING_SCALE = 3f
+private const val POP_ON_FLING_DELAY = 60L
+private const val POP_ON_FLING_SCALE = 2f
+private const val POP_ON_COMMITTED_SCALE = 3f
internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -774,7 +775,9 @@
GestureState.ENTRY,
GestureState.INACTIVE,
GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX
- else -> params.committedIndicator.scalePivotX
+ GestureState.ACTIVE -> params.activeIndicator.scalePivotX
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> params.committedIndicator.scalePivotX
},
horizontalTranslation = when (currentState) {
GestureState.GONE -> {
@@ -921,7 +924,7 @@
mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
} else {
- mView.popScale(POP_ON_FLING_SCALE)
+ mView.popScale(POP_ON_COMMITTED_SCALE)
mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
MIN_DURATION_COMMITTED_ANIMATION)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 6ce6f0d..c9d8c84 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -35,7 +35,7 @@
data class BackIndicatorDimens(
val horizontalTranslation: Float? = 0f,
val scale: Float = 0f,
- val scalePivotX: Float = 0f,
+ val scalePivotX: Float? = null,
val arrowDimens: ArrowDimens,
val backgroundDimens: BackgroundDimens,
val verticalTranslationSpring: SpringForce? = null,
@@ -203,7 +203,8 @@
horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
- scaleSpring = createSpring(450f, 0.415f),
+ scaleSpring = createSpring(450f, 0.39f),
+ scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
arrowDimens = ArrowDimens(
length = getDimen(R.dimen.navigation_edge_active_arrow_length),
height = getDimen(R.dimen.navigation_edge_active_arrow_height),
@@ -258,6 +259,7 @@
committedIndicator = activeIndicator.copy(
horizontalTranslation = null,
+ scalePivotX = null,
arrowDimens = activeIndicator.arrowDimens.copy(
lengthSpring = activeCommittedArrowLengthSpring,
heightSpring = activeCommittedArrowHeightSpring,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 44855fb..0f38d32 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
+import android.os.Build
import android.os.Bundle
+import android.os.UserHandle
import android.os.UserManager
+import android.util.Log
import androidx.activity.ComponentActivity
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
@@ -63,9 +65,13 @@
// | Bubble#showOrHideAppBubble | <--------------
// | (with WP user ID) |
// ----------------------------
- val mainUser: UserInfo? = userTracker.userProfiles.firstOrNull { it.isMain }
- if (userManager.isManagedProfile && mainUser != null) {
- controller.startNoteTaskProxyActivityForUser(mainUser.userHandle)
+ val mainUser: UserHandle? = userManager.mainUser
+ if (userManager.isManagedProfile) {
+ if (mainUser == null) {
+ logDebug { "Can't find the main user. Skipping the notes app launch." }
+ } else {
+ controller.startNoteTaskProxyActivityForUser(mainUser)
+ }
} else {
controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
}
@@ -83,3 +89,8 @@
}
}
}
+
+/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
+private inline fun Any.logDebug(message: () -> String) {
+ if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f62e939..630f6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -32,6 +32,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -113,6 +114,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
@@ -447,6 +449,10 @@
notifySystemUiStateFlags(mSysUiState.getFlags());
notifyConnectionChanged();
+ if (mLatchForOnUserChanging != null) {
+ mLatchForOnUserChanging.countDown();
+ mLatchForOnUserChanging = null;
+ }
}
@Override
@@ -501,11 +507,14 @@
}
};
+ private CountDownLatch mLatchForOnUserChanging;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
- public void onUserChanged(int newUser, @NonNull Context userContext) {
+ public void onUserChanging(int newUser, @NonNull Context userContext,
+ CountDownLatch latch) {
mConnectionBackoffAttempts = 0;
+ mLatchForOnUserChanging = latch;
internalConnectToCurrentUser("User changed");
}
};
@@ -676,11 +685,14 @@
}
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
- boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) {
+ boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+ boolean panelExpanded, boolean isDreaming) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
keyguardShowing && keyguardOccluded)
+ .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+ keyguardGoingAway)
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index d0b7ad3..3949492 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -27,9 +27,15 @@
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
import com.android.internal.statusbar.IAppClipsService;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Application;
@@ -37,6 +43,7 @@
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
@@ -46,21 +53,63 @@
*/
public class AppClipsService extends Service {
+ private static final String TAG = AppClipsService.class.getSimpleName();
+
@Application private final Context mContext;
private final FeatureFlags mFeatureFlags;
private final Optional<Bubbles> mOptionalBubbles;
private final DevicePolicyManager mDevicePolicyManager;
+ private final UserManager mUserManager;
+
private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
+ @VisibleForTesting()
+ @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile;
+
@Inject
public AppClipsService(@Application Context context, FeatureFlags featureFlags,
- Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
+ Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager,
+ UserManager userManager) {
mContext = context;
mFeatureFlags = featureFlags;
mOptionalBubbles = optionalBubbles;
mDevicePolicyManager = devicePolicyManager;
+ mUserManager = userManager;
+
+ // The consumer of this service are apps that call through StatusBarManager API to query if
+ // it can use app clips API. Since these apps can be launched as work profile users, this
+ // service will start as work profile user. SysUI doesn't share injected instances for
+ // different users. This is why the bubbles instance injected will be incorrect. As the apps
+ // don't generally have permission to connect to a service running as different user, we
+ // start a proxy connection to communicate with the main user's version of this service.
+ if (mUserManager.isManagedProfile()) {
+ // No need to check for prerequisites in this case as those are incorrect for work
+ // profile user instance of the service and the main user version of the service will
+ // take care of this check.
+ mAreTaskAndTimeIndependentPrerequisitesMet = false;
+
+ // Get the main user so that we can connect to the main user's version of the service.
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (mainUser == null) {
+ // If main user is not available there isn't much we can do, no apps can use app
+ // clips.
+ return;
+ }
+
+ // Set up the connection to be used later during onBind callback.
+ mProxyConnectorToMainProfile =
+ new ServiceConnector.Impl<>(
+ context,
+ new Intent(context, AppClipsService.class),
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_NOT_VISIBLE,
+ mainUser.getIdentifier(),
+ IAppClipsService.Stub::asInterface);
+ return;
+ }
mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
+ mProxyConnectorToMainProfile = null;
}
private boolean checkIndependentVariables() {
@@ -95,6 +144,13 @@
return new IAppClipsService.Stub() {
@Override
public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+ // In case of managed profile, use the main user's instance of the service. Callers
+ // cannot directly connect to the main user's instance as they may not have the
+ // permission to interact across users.
+ if (mUserManager.isManagedProfile()) {
+ return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
+ }
+
if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
return false;
}
@@ -107,4 +163,21 @@
}
};
}
+
+ /** Returns whether the app clips API can be used by querying the service as the main user. */
+ private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
+ if (mProxyConnectorToMainProfile == null) {
+ return false;
+ }
+
+ try {
+ AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult(
+ service -> service.canLaunchCaptureContentActivityForNote(taskId));
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.d(TAG, "Exception from service\n" + e);
+ }
+
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 3cb1a34..0487cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -40,6 +40,8 @@
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -79,13 +81,10 @@
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+ static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
+ static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+ static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private final DevicePolicyManager mDevicePolicyManager;
@@ -95,6 +94,7 @@
private final PackageManager mPackageManager;
private final UserTracker mUserTracker;
private final UiEventLogger mUiEventLogger;
+ private final UserManager mUserManager;
private final ResultReceiver mResultReceiver;
private Intent mKillAppClipsBroadcastIntent;
@@ -103,7 +103,7 @@
public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
- @Main Handler mainHandler) {
+ UserManager userManager, @Main Handler mainHandler) {
mDevicePolicyManager = devicePolicyManager;
mFeatureFlags = flags;
mOptionalBubbles = optionalBubbles;
@@ -111,6 +111,7 @@
mPackageManager = packageManager;
mUserTracker = userTracker;
mUiEventLogger = uiEventLogger;
+ mUserManager = userManager;
mResultReceiver = createResultReceiver(mainHandler);
}
@@ -123,6 +124,12 @@
return;
}
+ if (mUserManager.isManagedProfile()) {
+ maybeStartActivityForWPUser();
+ finish();
+ return;
+ }
+
if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
finish();
return;
@@ -191,6 +198,19 @@
}
}
+ private void maybeStartActivityForWPUser() {
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (mainUser == null) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ // Start the activity as the main user with activity result forwarding.
+ startActivityAsUser(
+ new Intent(this, AppClipsTrampolineActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
+ }
+
private void setErrorResultAndFinish(int errorCode) {
setResult(RESULT_OK,
new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b7243ae9..79d3b26 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -162,7 +162,7 @@
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.ClockAnimations;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
@@ -1627,9 +1627,9 @@
transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations();
- boolean customClockAnimation = clockAnims != null
- && clockAnims.getHasCustomPositionUpdatedAnimation();
+ ClockController clock = mKeyguardStatusViewController.getClockController();
+ boolean customClockAnimation = clock != null
+ && clock.getConfig().getHasCustomPositionUpdatedAnimation();
if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
// Find the clock, so we can exclude it from this transition.
@@ -5159,12 +5159,12 @@
Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
anim.addUpdateListener(animation -> {
- ClockAnimations clockAnims = mController.getClockAnimations();
- if (clockAnims == null) {
+ ClockController clock = mController.getClockController();
+ if (clock == null) {
return;
}
- clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction());
+ clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
});
return anim;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 156e4fd..af74c27 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -561,6 +561,7 @@
for (StatusBarWindowCallback cb : activeCallbacks) {
cb.onStateChanged(mCurrentState.keyguardShowing,
mCurrentState.keyguardOccluded,
+ mCurrentState.keyguardGoingAway,
mCurrentState.bouncerShowing,
mCurrentState.dozing,
mCurrentState.panelExpanded,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index f7790e8..7898736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -22,7 +22,6 @@
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
@@ -825,8 +824,7 @@
return false;
}
- if ((mSbn.getNotification().flags
- & FLAG_FOREGROUND_SERVICE) != 0) {
+ if (mSbn.getNotification().isFgsOrUij()) {
return true;
}
if (mSbn.getNotification().isMediaNotification()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index ca762fc..a48870b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -630,6 +630,11 @@
return false;
}
+ if (notification.isUserInitiatedJob()) {
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "user initiated job");
+ return false;
+ }
+
if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age);
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index cf5ecdd..6742e4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -163,7 +163,7 @@
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
+ private Set<BiometricUnlockEventsListener> mBiometricUnlockEventsListeners = new HashSet<>();
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
@@ -314,14 +314,14 @@
mKeyguardViewController = keyguardViewController;
}
- /** Adds a {@link BiometricModeListener}. */
- public void addBiometricModeListener(BiometricModeListener listener) {
- mBiometricModeListeners.add(listener);
+ /** Adds a {@link BiometricUnlockEventsListener}. */
+ public void addListener(BiometricUnlockEventsListener listener) {
+ mBiometricUnlockEventsListeners.add(listener);
}
- /** Removes a {@link BiometricModeListener}. */
- public void removeBiometricModeListener(BiometricModeListener listener) {
- mBiometricModeListeners.remove(listener);
+ /** Removes a {@link BiometricUnlockEventsListener}. */
+ public void removeListener(BiometricUnlockEventsListener listener) {
+ mBiometricUnlockEventsListeners.remove(listener);
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -387,7 +387,7 @@
@Override
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
- Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
+ Trace.beginSection("BiometricUnlockController#onBiometricUnlocked");
if (mUpdateMonitor.isGoingToSleep()) {
mLogger.deferringAuthenticationDueToSleep(userId,
biometricSourceType,
@@ -411,10 +411,15 @@
mKeyguardViewMediator.userActivity();
startWakeAndUnlock(biometricSourceType, isStrongBiometric);
} else {
- mLogger.d("onBiometricAuthenticated aborted by bypass controller");
+ mLogger.d("onBiometricUnlocked aborted by bypass controller");
}
}
+ /**
+ * Wake and unlock the device in response to successful authentication using biometrics.
+ * @param biometricSourceType Biometric source that was used to authenticate.
+ * @param isStrongBiometric
+ */
public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
int mode = calculateMode(biometricSourceType, isStrongBiometric);
@@ -422,6 +427,7 @@
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
|| mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
vibrateSuccess(biometricSourceType);
+ onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
}
startWakeAndUnlock(mode);
}
@@ -502,11 +508,17 @@
}
private void onModeChanged(@WakeAndUnlockMode int mode) {
- for (BiometricModeListener listener : mBiometricModeListeners) {
+ for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
listener.onModeChanged(mode);
}
}
+ private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) {
+ for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
+ listener.onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
+ }
+ }
+
public boolean hasPendingAuthentication() {
return mPendingAuthenticated != null
&& mUpdateMonitor
@@ -777,7 +789,7 @@
mMode = MODE_NONE;
mBiometricType = null;
mNotificationShadeWindowController.setForceDozeBrightness(false);
- for (BiometricModeListener listener : mBiometricModeListeners) {
+ for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
listener.onResetMode();
}
mNumConsecutiveFpFailures = 0;
@@ -895,10 +907,17 @@
}
/** An interface to interact with the {@link BiometricUnlockController}. */
- public interface BiometricModeListener {
+ public interface BiometricUnlockEventsListener {
/** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
default void onResetMode() {}
/** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
default void onModeChanged(@WakeAndUnlockMode int mode) {}
+
+ /**
+ * Called when the device is unlocked successfully using biometrics with the keyguard also
+ * being dismissed.
+ */
+ default void onBiometricUnlockedWithKeyguardDismissal(
+ BiometricSourceType biometricSourceType) { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index aabe0cb..0c8e9e56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1675,8 +1675,8 @@
mStatusBarStateController.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR);
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
- mBiometricUnlockController.addBiometricModeListener(
- new BiometricUnlockController.BiometricModeListener() {
+ mBiometricUnlockController.addListener(
+ new BiometricUnlockController.BiometricUnlockEventsListener() {
@Override
public void onResetMode() {
setWakeAndUnlocking(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 46603df..d3aa4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -405,6 +405,7 @@
if (mKeyguardStateController.isKeyguardFadingAway()) {
mStatusBarKeyguardViewManager.onKeyguardFadedAway();
}
+ dispatchScrimsVisible();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 30d2295..a8a834f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -558,8 +558,10 @@
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
+ Context mobileContext = mMobileContextProvider
+ .getMobileContextForSub(subId, mContext);
mDemoStatusIcons.addModernMobileView(
- mContext,
+ mobileContext,
mMobileIconsViewModel.getLogger(),
subId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index edfc95f..c623201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -618,9 +618,6 @@
if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
return false;
}
- if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return false;
- }
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 50cce45..6dc8065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -16,6 +16,12 @@
package com.android.systemui.statusbar.phone;
public interface StatusBarWindowCallback {
- void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
- boolean isDozing, boolean panelExpanded, boolean isDreaming);
+ /**
+ * Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
+ * Some of the flags passed as argument to the callback might have changed, but this is not
+ * guaranteed.
+ */
+ void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
+ boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+ boolean panelExpanded, boolean isDreaming);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index dfa6838..c73cc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -18,10 +18,10 @@
import android.app.ActivityManager
import android.app.IActivityManager
-import android.app.IUidObserver
import android.app.Notification
import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.app.PendingIntent
+import android.app.UidObserver
import android.content.Context
import android.util.Log
import android.view.View
@@ -319,7 +319,7 @@
}
/** Our implementation of a [IUidObserver]. */
- inner class CallAppUidObserver : IUidObserver.Stub() {
+ inner class CallAppUidObserver : UidObserver() {
/** True if the application managing the call is visible to the user. */
var isCallAppVisible: Boolean = false
private set
@@ -387,12 +387,6 @@
}
}
}
-
- override fun onUidGone(uid: Int, disabled: Boolean) {}
- override fun onUidActive(uid: Int) {}
- override fun onUidIdle(uid: Int, disabled: Boolean) {}
- override fun onUidCachedChanged(uid: Int, cached: Boolean) {}
- override fun onUidProcAdjChanged(uid: Int) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 90c32dc..3a11635 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -40,6 +40,9 @@
/** The subscriptionId that this connection represents */
val subId: Int
+ /** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */
+ val carrierId: StateFlow<Int>
+
/**
* The table log buffer created for this connection. Will have the name "MobileConnectionLog
* [subId]"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index fa71287..ea77163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -60,6 +60,13 @@
*/
val mobileIsDefault: StateFlow<Boolean>
+ /**
+ * True if the device currently has a carrier merged connection.
+ *
+ * See [CarrierMergedConnectionRepository] for more info.
+ */
+ val hasCarrierMergedConnection: Flow<Boolean>
+
/** True if the default network connection is validated and false otherwise. */
val defaultConnectionIsValidated: StateFlow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 44b5b3fa..eb20bba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -159,6 +159,15 @@
.flatMapLatest { it.mobileIsDefault }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value)
+ override val hasCarrierMergedConnection: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.hasCarrierMergedConnection }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.hasCarrierMergedConnection.value,
+ )
+
override val defaultConnectionIsValidated: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.defaultConnectionIsValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 809772e..6b86432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
@@ -25,6 +26,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_ID
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
@@ -52,6 +54,17 @@
override val tableLogBuffer: TableLogBuffer,
val scope: CoroutineScope,
) : MobileConnectionRepository {
+ private val _carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ override val carrierId =
+ _carrierId
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_ID,
+ _carrierId.value,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierId.value)
+
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly =
_isEmergencyOnly
@@ -186,6 +199,8 @@
dataEnabled.value = true
networkName.value = NetworkNameModel.IntentDerived(event.name)
+ _carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID
+
cdmaRoaming.value = event.roaming
_isRoaming.value = event.roaming
// TODO(b/261029387): not yet supported
@@ -208,6 +223,8 @@
// This is always true here, because we split out disabled states at the data-source level
dataEnabled.value = true
networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
+ // TODO(b/276943904): is carrierId a thing with carrier merged networks?
+ _carrierId.value = INVALID_SUBSCRIPTION_ID
numberOfLevels.value = event.numberOfLevels
cdmaRoaming.value = false
_primaryLevel.value = event.level
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 737bc68..0e4ceeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -160,6 +160,9 @@
override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
// TODO(b/261029387): not yet supported
+ override val hasCarrierMergedConnection = MutableStateFlow(false)
+
+ // TODO(b/261029387): not yet supported
override val defaultConnectionIsValidated: StateFlow<Boolean> = MutableStateFlow(true)
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 94d6d0b..a609917 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
@@ -157,6 +158,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected)
override val isRoaming = MutableStateFlow(false).asStateFlow()
+ override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow()
override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
override val isInService = MutableStateFlow(true).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index b3737ec..8869dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -109,6 +109,11 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
+ override val carrierId =
+ activeRepo
+ .flatMapLatest { it.carrierId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierId.value)
+
override val cdmaRoaming =
activeRepo
.flatMapLatest { it.cdmaRoaming }
@@ -321,13 +326,14 @@
}
companion object {
- const val COL_EMERGENCY = "emergencyOnly"
- const val COL_ROAMING = "roaming"
- const val COL_OPERATOR = "operatorName"
- const val COL_IS_IN_SERVICE = "isInService"
- const val COL_IS_GSM = "isGsm"
- const val COL_CDMA_LEVEL = "cdmaLevel"
- const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_CARRIER_ID = "carrierId"
const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_ROAMING = "roaming"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index d0c6215..b475183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
-import android.content.Context
+import android.content.Intent
import android.content.IntentFilter
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
@@ -31,6 +31,7 @@
import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
@@ -65,6 +66,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
@@ -75,7 +77,6 @@
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
- private val context: Context,
override val subId: Int,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
@@ -293,6 +294,23 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val carrierId =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED),
+ map = { intent, _ -> intent },
+ )
+ .filter { intent ->
+ intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ }
+ .map { it.carrierId() }
+ .onStart {
+ // Make sure we get the initial carrierId
+ emit(telephonyManager.simCarrierId)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId)
+
override val networkName: StateFlow<NetworkNameModel> =
broadcastDispatcher
.broadcastFlow(
@@ -317,7 +335,6 @@
@Inject
constructor(
private val broadcastDispatcher: BroadcastDispatcher,
- private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
private val carrierConfigRepository: CarrierConfigRepository,
@@ -332,7 +349,6 @@
networkNameSeparator: String,
): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
- context,
subId,
defaultNetworkName,
networkNameSeparator,
@@ -349,6 +365,9 @@
}
}
+private fun Intent.carrierId(): Int =
+ getIntExtra(TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID)
+
/**
* Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
* shared flow and then split them back out into other flows.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 45d50c1..0e9b6c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -59,6 +59,7 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -257,18 +258,32 @@
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
- // Because carrier merged networks are displayed as mobile networks, they're
- // part of the `isDefault` calculation. See b/272586234.
- .map { it.mobile.isDefault || it.carrierMerged.isDefault }
+ .map { it.mobile.isDefault }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
- columnPrefix = "",
+ columnPrefix = LOGGING_PREFIX,
columnName = "mobileIsDefault",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val hasCarrierMergedConnection: StateFlow<Boolean> =
+ combine(
+ connectivityRepository.defaultConnections,
+ carrierMergedSubId,
+ ) { defaultConnections, carrierMergedSubId ->
+ defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "hasCarrierMergedConnection",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
override val defaultConnectionIsValidated: StateFlow<Boolean> =
connectivityRepository.defaultConnections
.map { it.isValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 22351f8..b36ba384 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -16,15 +16,21 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.content.Context
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,8 +40,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
@@ -76,7 +80,7 @@
val alwaysUseCdmaLevel: StateFlow<Boolean>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: StateFlow<MobileIconGroup>
+ val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
/**
* Provider name for this network connection. The name can be one of 3 values:
@@ -119,10 +123,11 @@
override val mobileIsDefault: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
- defaultDataSubId: StateFlow<Int>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
+ private val context: Context,
+ val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) : MobileIconInteractor {
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
@@ -130,14 +135,14 @@
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
- private val isDefault =
- defaultDataSubId
- .mapLatest { connectionRepository.subId == it }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- connectionRepository.subId == defaultDataSubId.value
- )
+ // True if there exists _any_ icon override for this carrierId. Note that overrides can include
+ // any or none of the icon groups defined in MobileMappings, so we still need to check on a
+ // per-network-type basis whether or not the given icon group is overridden
+ private val carrierIdIconOverrideExists =
+ connectionRepository.carrierId
+ .map { carrierIdOverrides.carrierIdEntryExists(it) }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
@@ -157,35 +162,56 @@
connectionRepository.networkName.value
)
- /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
- override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
+ /** What the mobile icon would be before carrierId overrides */
+ private val defaultNetworkType: StateFlow<MobileIconGroup> =
combine(
connectionRepository.resolvedNetworkType,
defaultMobileIconMapping,
defaultMobileIconGroup,
- isDefault,
- ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
- if (!isDefault) {
- return@combine NOT_DEFAULT_DATA
- }
-
+ ) { resolvedNetworkType, mapping, defaultGroup ->
when (resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType ->
resolvedNetworkType.iconGroupOverride
- else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
+ else -> {
+ mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+ override val networkTypeIconGroup =
+ combine(
+ defaultNetworkType,
+ carrierIdIconOverrideExists,
+ ) { networkType, overrideExists ->
+ // DefaultIcon comes out of the icongroup lookup, we check for overrides here
+ if (overrideExists) {
+ val iconOverride =
+ carrierIdOverrides.getOverrideFor(
+ connectionRepository.carrierId.value,
+ networkType.name,
+ context.resources,
+ )
+ if (iconOverride > 0) {
+ OverriddenIcon(networkType, iconOverride)
+ } else {
+ DefaultIcon(networkType)
+ }
+ } else {
+ DefaultIcon(networkType)
}
}
.distinctUntilChanged()
- .onEach {
- // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
- // [Diffable] interface.
- tableLogBuffer.logChange(
- prefix = "",
- columnName = "networkTypeIcon",
- value = it.name
- )
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ initialValue = DefaultIcon(defaultMobileIconGroup.value),
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ DefaultIcon(defaultMobileIconGroup.value),
+ )
override val isEmergencyOnly = connectionRepository.isEmergencyOnly
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 6c8310a..eec91a0 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.content.Context
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
@@ -75,9 +76,6 @@
/** True if the CDMA level should be preferred over the primary level. */
val alwaysUseCdmaLevel: StateFlow<Boolean>
- /** Tracks the subscriptionId set as the default for data connections */
- val defaultDataSubId: StateFlow<Int>
-
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
@@ -112,9 +110,25 @@
connectivityRepository: ConnectivityRepository,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
+ private val context: Context,
) : MobileIconsInteractor {
- override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault
+ override val mobileIsDefault =
+ combine(
+ mobileConnectionsRepo.mobileIsDefault,
+ mobileConnectionsRepo.hasCarrierMergedConnection,
+ ) { mobileIsDefault, hasCarrierMergedConnection ->
+ // Because carrier merged networks are displayed as mobile networks, they're part of
+ // the `isDefault` calculation. See b/272586234.
+ mobileIsDefault || hasCarrierMergedConnection
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
mobileConnectionsRepo.activeMobileDataRepository
@@ -184,8 +198,6 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
- override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
-
/**
* Copied from the old pipeline. We maintain a 2s period of time where we will keep the
* validated bit from the old active network (A) while data is changing to the new one (B).
@@ -282,10 +294,10 @@
mobileIsDefault,
defaultMobileIconMapping,
defaultMobileIconGroup,
- defaultDataSubId,
isDefaultConnectionFailed,
isForceHidden,
mobileConnectionsRepo.getRepoForSubId(subId),
+ context,
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt
new file mode 100644
index 0000000..6ea5f90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.model
+
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/**
+ * A data wrapper class for [MobileIconGroup]. One lingering nuance of this pipeline is its
+ * dependency on MobileMappings for its lookup from NetworkType -> NetworkTypeIcon. And because
+ * MobileMappings is a static map of (netType, icon) that knows nothing of `carrierId`, we need the
+ * concept of a "default" or "overridden" icon type.
+ *
+ * Until we can remove that dependency on MobileMappings, we should just allow for the composition
+ * of overriding an icon id using the lookup defined in [MobileIconCarrierIdOverrides]. By using the
+ * [overrideIcon] method defined below, we can create any arbitrarily overridden network type icon.
+ */
+sealed interface NetworkTypeIconModel : Diffable<NetworkTypeIconModel> {
+ val contentDescription: Int
+ val iconId: Int
+ val name: String
+
+ data class DefaultIcon(
+ val iconGroup: MobileIconGroup,
+ ) : NetworkTypeIconModel {
+ override val contentDescription = iconGroup.dataContentDescription
+ override val iconId = iconGroup.dataType
+ override val name = iconGroup.name
+
+ override fun logDiffs(prevVal: NetworkTypeIconModel, row: TableRowLogger) {
+ if (prevVal !is DefaultIcon || prevVal.name != name) {
+ row.logChange(COL_NETWORK_ICON, name)
+ }
+ }
+ }
+
+ data class OverriddenIcon(
+ val iconGroup: MobileIconGroup,
+ override val iconId: Int,
+ ) : NetworkTypeIconModel {
+ override val contentDescription = iconGroup.dataContentDescription
+ override val name = iconGroup.name
+
+ override fun logDiffs(prevVal: NetworkTypeIconModel, row: TableRowLogger) {
+ if (prevVal !is OverriddenIcon || prevVal.name != name || prevVal.iconId != iconId) {
+ row.logChange(COL_NETWORK_ICON, "Ovrd($name)")
+ }
+ }
+ }
+
+ companion object {
+ const val COL_NETWORK_ICON = "networkTypeIcon"
+ }
+}
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 0fd007c..dce7bf2 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
@@ -146,11 +146,10 @@
combine(
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon,
iconInteractor.mobileIsDefault,
- ) { dataConnected, dataEnabled, failedConnection, alwaysShow, mobileIsDefault ->
- alwaysShow || (dataConnected && dataEnabled && !failedConnection && mobileIsDefault)
+ ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault ->
+ alwaysShow || (dataEnabled && dataConnected && mobileIsDefault)
}
.distinctUntilChanged()
.logDiffsForTable(
@@ -167,10 +166,13 @@
showNetworkTypeIcon,
) { networkTypeIconGroup, shouldShow ->
val desc =
- if (networkTypeIconGroup.dataContentDescription != 0)
- ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+ if (networkTypeIconGroup.contentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.contentDescription)
else null
- val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+ val icon =
+ if (networkTypeIconGroup.iconId != 0)
+ Icon.Resource(networkTypeIconGroup.iconId, desc)
+ else null
return@combine when {
!shouldShow -> null
else -> icon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 6479f3d..731f1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -44,11 +44,11 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.tuner.TunerService
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -68,12 +68,12 @@
val defaultConnections: StateFlow<DefaultConnectionModel>
}
-@OptIn(ExperimentalCoroutinesApi::class)
+@SuppressLint("MissingPermission")
@SysUISingleton
class ConnectivityRepositoryImpl
@Inject
constructor(
- connectivityManager: ConnectivityManager,
+ private val connectivityManager: ConnectivityManager,
private val connectivitySlots: ConnectivitySlots,
context: Context,
dumpManager: DumpManager,
@@ -144,15 +144,14 @@
) {
logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
+ val wifiInfo =
+ networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
val isWifiDefault =
- networkCapabilities.hasTransport(TRANSPORT_WIFI) ||
- networkCapabilities.getMainOrUnderlyingWifiInfo() != null
+ networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
val isMobileDefault =
networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
- val isCarrierMergedDefault =
- networkCapabilities
- .getMainOrUnderlyingWifiInfo()
- ?.isCarrierMerged == true
+ val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
val isEthernetDefault =
networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
@@ -209,7 +208,32 @@
* always use [WifiInfo] if it's available, so we need to check the underlying transport
* info.
*/
- fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? {
+ fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
+ connectivityManager: ConnectivityManager,
+ ): WifiInfo? {
+ val mainWifiInfo = this.getMainWifiInfo()
+ if (mainWifiInfo != null) {
+ return mainWifiInfo
+ }
+ // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
+ // so skip the underlying network check if it's not CELLULAR.
+ if (!this.hasTransport(TRANSPORT_CELLULAR)) {
+ return mainWifiInfo
+ }
+
+ // Some connections, like VPN connections, may have underlying networks that are
+ // eventually traced to a wifi or carrier merged connection. So, check those underlying
+ // networks for possible wifi information as well. See b/225902574.
+ return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
+ connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+ }
+ }
+
+ /**
+ * Checks the network capabilities for wifi info, but does *not* check the underlying
+ * networks. See [getMainOrUnderlyingWifiInfo].
+ */
+ private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index f80aa68..b37c44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -138,7 +138,8 @@
wifiNetworkChangeEvents.tryEmit(Unit)
- val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo()
+ val wifiInfo =
+ networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel =
createWifiNetworkModel(
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
index f3b7e0d..298dacd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
@@ -26,4 +26,5 @@
/**
* Assists type-checking to unpack a Java Optional into T?
*/
+@Suppress("NOTHING_TO_INLINE")
inline fun <T> Optional<T>.getOrNull(): T? = orElse(null)
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index c6da55c..968dcc9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -25,6 +25,7 @@
import android.util.Log;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -108,6 +109,7 @@
private final Context mContext;
private final Intent mServiceIntent;
+ private final UserTracker mUserTracker;
private final int mFlags;
private final Executor mExecutor;
private final ServiceTransformer<T> mTransformer;
@@ -127,10 +129,12 @@
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
+ UserTracker userTracker,
@Main Executor executor,
ServiceTransformer<T> transformer) {
mContext = context;
mServiceIntent = serviceIntent;
+ mUserTracker = userTracker;
mFlags = Context.BIND_AUTO_CREATE;
mExecutor = executor;
mTransformer = transformer;
@@ -145,7 +149,8 @@
public boolean bind() {
boolean bindResult = false;
try {
- bindResult = mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+ bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
+ mUserTracker.getUserHandle());
} catch (SecurityException e) {
Log.d(TAG, "Could not bind to service", e);
mContext.unbindService(this);
@@ -228,11 +233,13 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) {
- Log.d(TAG, "onServiceConnected");
- }
- mProxy = mTransformer.convert(service);
- applyToCallbacksLocked(callback -> callback.onConnected(this, mProxy));
+ mExecutor.execute(() -> {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnected");
+ }
+ mProxy = mTransformer.convert(service);
+ applyToCallbacksLocked(callback -> callback.onConnected(this, mProxy));
+ });
}
private void applyToCallbacksLocked(Consumer<Callback<T>> applicator) {
@@ -250,16 +257,16 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- onDisconnected(DISCONNECT_REASON_DISCONNECTED);
+ mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
}
@Override
public void onBindingDied(ComponentName name) {
- onDisconnected(DISCONNECT_REASON_DISCONNECTED);
+ mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
}
@Override
public void onNullBinding(ComponentName name) {
- onDisconnected(DISCONNECT_REASON_NULL_BINDING);
+ mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 54b3030..cd1ad1b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -125,6 +125,8 @@
private int mBitmapUsages = 0;
private final Object mLock = new Object();
+ private boolean mIsLockscreenLiveWallpaperEnabled;
+
CanvasEngine() {
super();
setFixedSizeAllowed(true);
@@ -167,8 +169,10 @@
Log.d(TAG, "onCreate");
}
mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+ mIsLockscreenLiveWallpaperEnabled = mWallpaperManager
+ .isLockscreenLiveWallpaperEnabled();
mSurfaceHolder = surfaceHolder;
- Rect dimensions = mWallpaperManager.isLockscreenLiveWallpaperEnabled()
+ Rect dimensions = mIsLockscreenLiveWallpaperEnabled
? mWallpaperManager.peekBitmapDimensions(getSourceFlag())
: mWallpaperManager.peekBitmapDimensions();
int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
@@ -319,7 +323,7 @@
boolean loadSuccess = false;
Bitmap bitmap;
try {
- bitmap = mWallpaperManager.isLockscreenLiveWallpaperEnabled()
+ bitmap = mIsLockscreenLiveWallpaperEnabled
? mWallpaperManager.getBitmapAsUser(
mUserTracker.getUserId(), false, getSourceFlag())
: mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
@@ -333,7 +337,7 @@
// be loaded, we will go into a cycle. Don't do a build where the
// default wallpaper can't be loaded.
Log.w(TAG, "Unable to load wallpaper!", exception);
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
} else {
mWallpaperManager.clearWallpaper(
@@ -341,7 +345,7 @@
}
try {
- bitmap = mWallpaperManager.isLockscreenLiveWallpaperEnabled()
+ bitmap = mIsLockscreenLiveWallpaperEnabled
? mWallpaperManager.getBitmapAsUser(
mUserTracker.getUserId(), false, getSourceFlag())
: mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
@@ -365,7 +369,7 @@
mBitmap.recycle();
}
mBitmap = bitmap;
- mWideColorGamut = mWallpaperManager.isLockscreenLiveWallpaperEnabled()
+ mWideColorGamut = mIsLockscreenLiveWallpaperEnabled
? mWallpaperManager.wallpaperSupportsWcg(getSourceFlag())
: mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 08f7eae..a4b093d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -241,8 +241,8 @@
// Store callback in a field so it won't get GC'd
mStatusBarWindowCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded,
- isDreaming) ->
+ (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
+ panelExpanded, isDreaming) ->
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 480b8f9..a9920ec7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceConfig
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
@@ -101,8 +102,10 @@
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)
+ whenever(smallClockController.config)
+ .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
+ whenever(largeClockController.config)
+ .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
repository = FakeKeyguardRepository()
bouncerRepository = FakeKeyguardBouncerRepository()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index b15ac39..2f627cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -315,8 +315,8 @@
}
@Test
- public void testGetClockAnimationsForwardsToClock() {
- assertEquals(mClockAnimations, mController.getClockAnimations());
+ public void testGetClock_ForwardsToClock() {
+ assertEquals(mClockController, mController.getClock());
}
@Test
@@ -367,9 +367,9 @@
}
@Test
- public void testGetClockAnimations_nullClock_returnsNull() {
+ public void testGetClock_nullClock_returnsNull() {
when(mClockEventController.getClock()).thenReturn(null);
- assertNull(mController.getClockAnimations());
+ assertNull(mController.getClock());
}
private void verifyAttachment(VerificationMode times) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 65f8610..d760189 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -163,8 +163,6 @@
@Captor
private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
- private Configuration mConfiguration;
-
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
private KeyguardPasswordViewController mKeyguardPasswordViewController;
private KeyguardPasswordView mKeyguardPasswordView;
@@ -172,12 +170,12 @@
@Before
public void setup() {
- mConfiguration = new Configuration();
- mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED.
mTestableResources = mContext.getOrCreateTestableResources();
+ mTestableResources.getResources().getConfiguration().orientation =
+ Configuration.ORIENTATION_UNDEFINED;
when(mView.getContext()).thenReturn(mContext);
- when(mView.getResources()).thenReturn(mContext.getResources());
+ when(mView.getResources()).thenReturn(mTestableResources.getResources());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */
0);
lp.gravity = 0;
@@ -254,6 +252,8 @@
@Test
public void onResourcesUpdate_callsThroughOnRotationChange() {
+ clearInvocations(mView);
+
// Rotation is the same, shouldn't cause an update
mKeyguardSecurityContainerController.updateResources();
verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
@@ -620,12 +620,12 @@
// Set initial gravity
mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
Gravity.CENTER);
+ mTestableResources.addOverride(
+ R.bool.can_use_one_handed_bouncer, false);
// Kick off the initial pass...
mKeyguardSecurityContainerController.onInit();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == Gravity.CENTER));
+ verify(mView).setLayoutParams(any());
clearInvocations(mView);
// Now simulate a config change
@@ -633,9 +633,7 @@
Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
mKeyguardSecurityContainerController.updateResources();
- verify(mView).setLayoutParams(argThat(
- (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
- argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
+ verify(mView).setLayoutParams(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 7144914..48f7d92 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,17 +16,17 @@
package com.android.keyguard;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.graphics.Rect;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockAnimations;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -118,14 +118,10 @@
}
@Test
- public void getClockAnimations_forwardsToClockSwitch() {
- ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
- when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations);
+ public void getClock_forwardsToClockSwitch() {
+ ClockController mockClock = mock(ClockController.class);
+ when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- Rect r1 = new Rect(1, 2, 3, 4);
- Rect r2 = new Rect(5, 6, 7, 8);
- mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f);
-
- verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f);
+ assertEquals(mockClock, mController.getClockController());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index a245c01..7d9ccb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
@@ -50,6 +49,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
@@ -73,16 +73,28 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock private lateinit var bypassController: KeyguardBypassController
- @Mock private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
- @Mock private lateinit var udfpsController: UdfpsController
- @Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var lightRevealScrim: LightRevealScrim
- @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
+ @Mock
+ private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock
+ private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock
+ private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock
+ private lateinit var udfpsControllerProvider: Provider<UdfpsController>
+ @Mock
+ private lateinit var udfpsController: UdfpsController
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var lightRevealScrim: LightRevealScrim
+ @Mock
+ private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
+
+ @Captor
+ private lateinit var biometricUnlockListener:
+ ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
@Before
fun setUp() {
@@ -106,13 +118,12 @@
wakefulnessLifecycle,
commandRegistry,
notificationShadeWindowController,
- bypassController,
- biometricUnlockController,
udfpsControllerProvider,
statusBarStateController,
featureFlags,
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
- rippleView
+ biometricUnlockController,
+ rippleView,
)
controller.init()
`when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim)
@@ -134,12 +145,9 @@
eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN fingerprint authenticated
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FINGERPRINT /* type */,
- false /* isStrongBiometric */)
+ verify(biometricUnlockController).addListener(biometricUnlockListener.capture())
+ biometricUnlockListener.value
+ .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT)
// THEN update sensor location and show ripple
verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
@@ -191,51 +199,6 @@
}
@Test
- fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
- val faceLocation = Point(5, 5)
- `when`(authController.faceSensorLocation).thenReturn(faceLocation)
- controller.onViewAttached()
-
- `when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
- BiometricSourceType.FACE)).thenReturn(true)
-
- // WHEN bypass is enabled & face authenticated
- `when`(bypassController.canBypass()).thenReturn(true)
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FACE /* type */,
- false /* isStrongBiometric */)
-
- // THEN show ripple
- verify(rippleView).setSensorLocation(faceLocation)
- verify(rippleView).startUnlockedRipple(any())
- }
-
- @Test
- fun testFaceTriggerNonBypass_NoRipple() {
- // GIVEN face auth sensor exists
- val faceLocation = Point(5, 5)
- `when`(authController.faceSensorLocation).thenReturn(faceLocation)
- controller.onViewAttached()
-
- // WHEN bypass isn't enabled & face authenticated
- `when`(bypassController.canBypass()).thenReturn(false)
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FACE /* type */,
- false /* isStrongBiometric */)
-
- // THEN no ripple
- verify(rippleView, never()).startUnlockedRipple(any())
- }
-
- @Test
fun testNullFaceSensorLocationDoesNothing() {
`when`(authController.faceSensorLocation).thenReturn(null)
controller.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 94489ad..1f2b64d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -191,18 +191,11 @@
foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
- var aod: Boolean? = null
- var awake: Boolean? = null
var folded: Int? = null
var displayState: Int? = null
val job =
interactor.addBiometricContextListener(
object : IBiometricContextListener.Stub() {
- override fun onDozeChanged(isAod: Boolean, isAwake: Boolean) {
- aod = isAod
- awake = isAwake
- }
-
override fun onFoldChanged(foldState: Int) {
folded = foldState
}
@@ -214,8 +207,6 @@
)
runCurrent()
- assertThat(aod).isTrue()
- assertThat(awake).isFalse()
assertThat(folded).isEqualTo(FoldState.FULLY_CLOSED)
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
@@ -224,8 +215,6 @@
keyguardTransitionRepository.startTransitionTo(KeyguardState.LOCKSCREEN)
runCurrent()
- assertThat(aod).isFalse()
- assertThat(awake).isTrue()
assertThat(folded).isEqualTo(FoldState.HALF_OPENED)
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)
@@ -236,8 +225,6 @@
keyguardTransitionRepository.startTransitionTo(KeyguardState.AOD)
runCurrent()
- assertThat(aod).isFalse()
- assertThat(awake).isTrue()
assertThat(folded).isEqualTo(FoldState.HALF_OPENED)
assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)
}
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 fd6e31b..1851582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,8 +16,6 @@
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;
@@ -40,7 +38,6 @@
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;
@@ -65,7 +62,6 @@
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private UiEventLogger mUiEventLogger;
@@ -99,10 +95,8 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
-
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mUiEventLogger);
}
@@ -222,34 +216,4 @@
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/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index ffd75fb..8600b7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -20,9 +20,11 @@
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_EXPANDED_FROM_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
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;
@@ -120,7 +122,6 @@
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,
@@ -143,178 +144,6 @@
}
@Test
- public void test_setClipData_invalidImageData_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- ClipData clipData = new ClipData("", new String[]{"image/png"},
- new ClipData.Item(Uri.parse("")));
-
- mOverlayController.setClipDataLegacy(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_nonImageUri_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- ClipData clipData = new ClipData("", new String[]{"resource/png"},
- new ClipData.Item(Uri.parse("")));
-
- mOverlayController.setClipDataLegacy(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_textData_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- when(mAnimator.isRunning()).thenReturn(true);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_viewCallbacks_onShareTapped_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- 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_MINIMIZED_LAYOUT, false);
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mTimeoutHandler).resetTimeout();
- }
-
- @Test
- public void test_nonRemoteCopy_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- 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() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- 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_invalidImageData() {
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
@@ -340,9 +169,10 @@
@Test
public void test_setClipData_textData() {
- mOverlayController.setClipData(mSampleClipData, "");
+ mOverlayController.setClipData(mSampleClipData, "abc");
verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "abc");
verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@@ -440,6 +270,8 @@
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "second.package");
verifyNoMoreInteractions(mUiEventLogger);
}
@@ -465,6 +297,7 @@
mExecutor.runAllReady();
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "actionShownSource");
verifyNoMoreInteractions(mUiEventLogger);
}
@@ -481,11 +314,12 @@
public void test_insets_showsMinimized() {
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 1)));
- mOverlayController.setClipData(mSampleClipData, "");
+ mOverlayController.setClipData(mSampleClipData, "abc");
Animator mockFadeoutAnimator = Mockito.mock(Animator.class);
when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(true);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED, 0, "abc");
verify(mClipboardOverlayView, never()).setMinimized(false);
verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
@@ -495,6 +329,8 @@
verify(mClipboardOverlayView).setMinimized(false);
verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED, 0, "abc");
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "abc");
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index 3d8f04e..673b5eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -142,81 +142,6 @@
assertEquals(actionB, result);
}
- // TODO(b/267162944): Next four tests (marked "legacy") are obsolete once
- // CLIPBOARD_MINIMIZED_LAYOUT flag is released and removed
- @Test
- public void test_getAction_noLinks_returnsEmptyOptional_legacy() {
- ClipData.Item item = new ClipData.Item("no text links");
- item.setTextLinks(Mockito.mock(TextLinks.class));
-
- Optional<RemoteAction> action = mClipboardUtils.getAction(item, "");
-
- assertTrue(action.isEmpty());
- }
-
- @Test
- public void test_getAction_returnsFirstLink_legacy() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
- when(mClipDataItem.getText()).thenReturn("");
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
- classificationA, classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
- assertEquals(actionA, result);
- }
-
- @Test
- public void test_getAction_skipsMatchingComponent_legacy() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
- when(mClipDataItem.getText()).thenReturn("");
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
- classificationA, classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "abc").orElse(null);
-
- assertEquals(actionB, result);
- }
-
- @Test
- public void test_getAction_skipsShortEntity_legacy() {
- TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
- final Map<String, Float> scores = new ArrayMap<>();
- scores.put(TextClassifier.TYPE_EMAIL, 1f);
- textLinks.addLink(20, 22, scores);
- textLinks.addLink(0, 22, scores);
-
- when(mClipDataItem.getTextLinks()).thenReturn(textLinks.build());
- when(mClipDataItem.getText()).thenReturn(textLinks.build().getText());
-
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
- classificationA);
- when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
- classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
- assertEquals(actionB, result);
- }
-
@Test
public void test_extra_withPackage_returnsTrue() {
PersistableBundle b = new PersistableBundle();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt
new file mode 100644
index 0000000..7753197
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.contrast
+
+import android.app.UiModeManager
+import android.app.UiModeManager.ContrastUtils.fromContrastLevel
+import android.os.Looper
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Test the behaviour of buttons of the [ContrastDialog]. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ContrastDialogTest : SysuiTestCase() {
+
+ private lateinit var mainExecutor: Executor
+ private lateinit var contrastDialog: ContrastDialog
+ @Mock private lateinit var mockUiModeManager: UiModeManager
+ @Mock private lateinit var mockUserTracker: UserTracker
+ @Mock private lateinit var mockSecureSettings: SecureSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mainExecutor = MoreExecutors.directExecutor()
+ whenever(mockUserTracker.userId).thenReturn(context.userId)
+ }
+
+ @Test
+ fun testClickButtons_putsContrastInSettings() {
+ if (Looper.myLooper() == null) Looper.prepare()
+ contrastDialog =
+ ContrastDialog(
+ context,
+ mainExecutor,
+ mockUiModeManager,
+ mockUserTracker,
+ mockSecureSettings
+ )
+ contrastDialog.show()
+ try {
+ contrastDialog.contrastButtons.forEach {
+ (contrastLevel: Int, clickedButton: FrameLayout) ->
+ clickedButton.performClick()
+ verify(mockSecureSettings)
+ .putFloatForUser(
+ eq(Settings.Secure.CONTRAST_LEVEL),
+ eq(fromContrastLevel(contrastLevel)),
+ eq(context.userId)
+ )
+ }
+ } finally {
+ contrastDialog.dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 6884616..f4cc8bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -60,6 +60,12 @@
}
val TEST_STRUCTURE: CharSequence = "TestStructure"
val TEST_APP: CharSequence = "TestApp"
+
+ private fun View.waitForPost() {
+ val latch = CountDownLatch(1)
+ post(latch::countDown)
+ latch.await()
+ }
}
@Main private val executor: Executor = MoreExecutors.directExecutor()
@@ -140,7 +146,10 @@
val rearrangeButton = requireViewById<Button>(R.id.rearrange)
assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
assertThat(rearrangeButton.isEnabled).isFalse()
- assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.GONE)
+
+ val otherAppsButton = requireViewById<Button>(R.id.other_apps)
+ otherAppsButton.waitForPost()
+ assertThat(otherAppsButton.visibility).isEqualTo(View.GONE)
}
}
@@ -160,7 +169,10 @@
val rearrangeButton = requireViewById<Button>(R.id.rearrange)
assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
assertThat(rearrangeButton.isEnabled).isFalse()
- assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.VISIBLE)
+
+ val otherAppsButton = requireViewById<Button>(R.id.other_apps)
+ otherAppsButton.waitForPost()
+ assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 0e6f8d4..3fd97da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -46,10 +46,12 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -59,6 +61,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRepositoryImplTest : SysuiTestCase() {
@@ -73,6 +76,9 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
+ private val mainDispatcher = StandardTestDispatcher()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var underTest: KeyguardRepositoryImpl
@@ -92,12 +98,13 @@
dozeParameters,
authController,
dreamOverlayCallbackController,
+ mainDispatcher
)
}
@Test
fun animateBottomAreaDozingTransitions() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
underTest.setAnimateDozingTransitions(true)
@@ -112,7 +119,7 @@
@Test
fun bottomAreaAlpha() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
underTest.setBottomAreaAlpha(0.1f)
@@ -133,7 +140,7 @@
@Test
fun clockPosition() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
underTest.setClockPosition(0, 1)
@@ -151,11 +158,12 @@
@Test
fun isKeyguardShowing() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(keyguardStateController.isShowing).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
assertThat(underTest.isKeyguardShowing()).isFalse()
@@ -164,11 +172,13 @@
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
+ runCurrent()
assertThat(latest).isTrue()
assertThat(underTest.isKeyguardShowing()).isTrue()
whenever(keyguardStateController.isShowing).thenReturn(false)
captor.value.onKeyguardShowingChanged()
+ runCurrent()
assertThat(latest).isFalse()
assertThat(underTest.isKeyguardShowing()).isFalse()
@@ -197,11 +207,12 @@
@Test
fun isKeyguardOccluded() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(keyguardStateController.isOccluded).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -209,10 +220,12 @@
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
+ runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isOccluded).thenReturn(false)
captor.value.onKeyguardShowingChanged()
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -220,11 +233,12 @@
@Test
fun isKeyguardUnlocked() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -232,10 +246,12 @@
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
+ runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isUnlocked).thenReturn(false)
captor.value.onUnlockedChanged()
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -243,82 +259,98 @@
@Test
fun isDozing() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ runCurrent()
val captor = argumentCaptor<DozeHost.Callback>()
verify(dozeHost).addCallback(captor.capture())
captor.value.onDozingChanged(true)
+ runCurrent()
assertThat(latest).isTrue()
captor.value.onDozingChanged(false)
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
+ runCurrent()
verify(dozeHost).removeCallback(captor.value)
}
@Test
fun `isDozing - starts with correct initial value for isDozing`() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
var latest: Boolean? = null
whenever(statusBarStateController.isDozing).thenReturn(true)
var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isTrue()
job.cancel()
whenever(statusBarStateController.isDozing).thenReturn(false)
job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun dozeAmount() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<Float>()
val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ runCurrent()
verify(statusBarStateController).addCallback(captor.capture())
captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ runCurrent()
captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ runCurrent()
captor.value.onDozeAmountChanged(0.661f, 0.65f)
+ runCurrent()
assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
+ runCurrent()
verify(statusBarStateController).removeCallback(captor.value)
}
@Test
fun wakefulness() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulness.onEach(values::add).launchIn(this)
+ runCurrent()
val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
verify(wakefulnessLifecycle).addObserver(captor.capture())
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
captor.value.onStartedWakingUp()
+ runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
captor.value.onFinishedWakingUp()
+ runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
captor.value.onStartedGoingToSleep()
+ runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
captor.value.onFinishedGoingToSleep()
+ runCurrent()
assertThat(values.map { it.state })
.isEqualTo(
@@ -333,12 +365,13 @@
)
job.cancel()
+ runCurrent()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
@Test
fun isUdfpsSupported() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
assertThat(underTest.isUdfpsSupported()).isTrue()
@@ -348,11 +381,11 @@
@Test
fun isKeyguardGoingAway() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
-
+ runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -360,10 +393,12 @@
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()
+ runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
captor.value.onKeyguardGoingAwayChanged()
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -371,20 +406,23 @@
@Test
fun isDreamingFromKeyguardUpdateMonitor() =
- runTest(UnconfinedTestDispatcher()) {
+ TestScope(mainDispatcher).runTest {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onDreamingStateChanged(true)
+ runCurrent()
assertThat(latest).isTrue()
captor.value.onDreamingStateChanged(false)
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -392,11 +430,12 @@
@Test
fun isDreamingFromDreamOverlayCallbackController() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
val listener =
@@ -405,9 +444,11 @@
}
listener.onStartDream()
+ runCurrent()
assertThat(latest).isTrue()
listener.onWakeUp()
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -415,12 +456,13 @@
@Test
fun biometricUnlockState() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<BiometricUnlockModel>()
val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
- verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+ runCurrent()
+ val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>()
+ verify(biometricUnlockController).addListener(captor.capture())
listOf(
BiometricUnlockController.MODE_NONE,
@@ -435,6 +477,7 @@
.forEach {
whenever(biometricUnlockController.mode).thenReturn(it)
captor.value.onModeChanged(it)
+ runCurrent()
}
assertThat(values)
@@ -454,12 +497,13 @@
)
job.cancel()
- verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ runCurrent()
+ verify(biometricUnlockController).removeListener(captor.value)
}
@Test
fun dozeTransitionModel() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
// For the initial state
whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
@@ -467,6 +511,7 @@
val values = mutableListOf<DozeTransitionModel>()
val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
+ runCurrent()
val listener =
withArgCaptor<DozeTransitionCallback> {
verify(dozeTransitionListener).addCallback(capture())
@@ -475,20 +520,26 @@
// These don't have to reflect real transitions from the DozeMachine. Only that the
// transitions are properly emitted
listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
+ runCurrent()
listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
+ runCurrent()
listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
+ runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_REQUEST_PULSE,
DozeMachine.State.DOZE_PULSING
)
+ runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
DozeMachine.State.DOZE_PULSE_DONE
)
+ runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_AOD_PAUSING,
DozeMachine.State.DOZE_AOD_PAUSED
)
+ runCurrent()
assertThat(values)
.isEqualTo(
@@ -517,15 +568,17 @@
)
job.cancel()
+ runCurrent()
verify(dozeTransitionListener).removeCallback(listener)
}
@Test
fun fingerprintSensorLocation() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
+ runCurrent()
val captor = argumentCaptor<AuthController.Callback>()
verify(authController).addCallback(captor.capture())
@@ -539,6 +592,7 @@
.onEach {
whenever(authController.fingerprintSensorLocation).thenReturn(it)
captor.value.onFingerprintLocationChanged()
+ runCurrent()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
@@ -549,11 +603,12 @@
@Test
fun faceSensorLocation() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
val captor = argumentCaptor<AuthController.Callback>()
+ runCurrent()
verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
@@ -571,6 +626,7 @@
.onEach {
whenever(authController.faceSensorLocation).thenReturn(it)
captor.value.onFaceSensorLocationChanged()
+ runCurrent()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
@@ -581,10 +637,11 @@
@Test
fun biometricUnlockSource() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val values = mutableListOf<BiometricUnlockSource?>()
val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
+ runCurrent()
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
@@ -603,6 +660,7 @@
)
.onEach { biometricSourceType ->
captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
+ runCurrent()
}
assertThat(values)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 4c8a0a5..24a47b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -48,6 +48,7 @@
import org.mockito.Answers
import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -124,6 +125,17 @@
}
@Test
+ fun testShow_isResumed() {
+ whenever(repository.primaryBouncerShow.value).thenReturn(true)
+ whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+ .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
+
+ underTest.show(true)
+ verify(repository).setPrimaryShow(false)
+ verify(repository).setPrimaryShow(true)
+ }
+
+ @Test
fun testHide() {
underTest.hide()
verify(falsingCollector).onBouncerHidden()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index 2026006..b40ebc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -24,12 +24,14 @@
import android.view.View.VISIBLE
import android.widget.FrameLayout
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
@@ -39,8 +41,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -61,9 +64,16 @@
private lateinit var keyguardMediaController: KeyguardMediaController
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
+ private lateinit var statusBarStateListener: StatusBarStateController.StateListener
@Before
fun setup() {
+ doAnswer {
+ statusBarStateListener = it.arguments[0] as StatusBarStateController.StateListener
+ return@doAnswer Unit
+ }
+ .whenever(statusBarStateController)
+ .addCallback(any(StatusBarStateController.StateListener::class.java))
// default state is positive, media should show up
whenever(mediaHost.visible).thenReturn(true)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
@@ -170,4 +180,31 @@
fun testMediaHost_expandedPlayer() {
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
+
+ @Test
+ fun dozing_inSplitShade_mediaIsHidden() {
+ val splitShadeContainer = FrameLayout(context)
+ keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+ keyguardMediaController.useSplitShade = true
+
+ setDozing()
+
+ assertThat(splitShadeContainer.visibility).isEqualTo(GONE)
+ }
+
+ @Test
+ fun dozing_inSingleShade_mediaIsVisible() {
+ val splitShadeContainer = FrameLayout(context)
+ keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+ keyguardMediaController.useSplitShade = false
+
+ setDozing()
+
+ assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
+ }
+
+ private fun setDozing() {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ statusBarStateListener.onDozingChanged(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 17d8799..7f7952f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -532,7 +532,7 @@
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index c96853d..a0c376f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import org.junit.After
@@ -38,6 +39,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -86,15 +88,28 @@
@Test
fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
+ val mainUserHandle: UserHandle = mainUser.userHandle
userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
whenever(userManager.isManagedProfile).thenReturn(true)
+ whenever(userManager.mainUser).thenReturn(mainUserHandle)
activityRule.launchActivity(/* startIntent= */ null)
- val mainUserHandle: UserHandle = mainUser.userHandle
verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
}
+ @Test
+ fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
+ userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
+ whenever(userManager.isManagedProfile).thenReturn(true)
+ whenever(userManager.mainUser).thenReturn(null)
+
+ activityRule.launchActivity(/* startIntent= */ null)
+
+ verify(noteTaskController, never()).showNoteTask(any())
+ verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
+ }
+
private companion object {
val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
val workProfileUser =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index b55fe36..67b1099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -20,8 +20,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
@@ -29,6 +31,8 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import androidx.test.runner.AndroidJUnit4;
@@ -42,6 +46,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -58,6 +63,9 @@
@Mock private Optional<Bubbles> mOptionalBubbles;
@Mock private Bubbles mBubbles;
@Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock private UserManager mUserManager;
+
+ private AppClipsService mAppClipsService;
@Before
public void setUp() {
@@ -119,26 +127,53 @@
@Test
public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
- when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ mockToSatisfyAllPrerequisites();
assertThat(getInterfaceWithRealContext()
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
}
+ @Test
+ public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+ IAppClipsService service = getInterfaceWithRealContext();
+ mAppClipsService.mProxyConnectorToMainProfile =
+ Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+
+ service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
+
+ verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+ }
+
+ @Test
+ public void isManagedProfile_noMainUser_shouldReturnFalse() {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(null);
+
+ getInterfaceWithRealContext();
+
+ assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+ }
+
+ private void mockToSatisfyAllPrerequisites() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ }
+
private IAppClipsService getInterfaceWithRealContext() {
- AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager);
- return getInterfaceFromService(appClipsService);
+ mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ return getInterfaceFromService(mAppClipsService);
}
private IAppClipsService getInterfaceWithMockContext() {
- AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager);
- return getInterfaceFromService(appClipsService);
+ mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ return getInterfaceFromService(mAppClipsService);
}
private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index ad06dcc..31a33d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -49,6 +49,8 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import androidx.test.rule.ActivityTestRule;
@@ -98,6 +100,9 @@
private UserTracker mUserTracker;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserManager mUserManager;
+
@Main
private Handler mMainHandler;
@@ -109,7 +114,7 @@
protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
- mUserTracker, mUiEventLogger, mMainHandler);
+ mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
}
};
@@ -264,6 +269,40 @@
verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
}
+ @Test
+ public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
+ throws NameNotFoundException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+ mockToSatisfyAllPrerequisites();
+
+ AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ Intent actualIntent = activity.mStartedIntent;
+ assertThat(actualIntent.getComponent()).isEqualTo(
+ new ComponentName(mContext, AppClipsTrampolineActivity.class));
+ assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
+ }
+
+ @Test
+ public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
+ throws NameNotFoundException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(null);
+
+ mockToSatisfyAllPrerequisites();
+
+ mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -282,6 +321,9 @@
public static final class AppClipsTrampolineActivityTestable extends
AppClipsTrampolineActivity {
+ Intent mStartedIntent;
+ UserHandle mStartingUser;
+
public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
FeatureFlags flags,
Optional<Bubbles> optionalBubbles,
@@ -289,9 +331,10 @@
PackageManager packageManager,
UserTracker userTracker,
UiEventLogger uiEventLogger,
+ UserManager userManager,
@Main Handler mainHandler) {
super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
- userTracker, uiEventLogger, mainHandler);
+ userTracker, uiEventLogger, userManager, mainHandler);
}
@Override
@@ -303,6 +346,12 @@
public void startActivity(Intent unUsed) {
// Ignore this intent to avoid App Clips screenshot editing activity from starting.
}
+
+ @Override
+ public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) {
+ mStartedIntent = startedIntent;
+ mStartingUser = startingUser;
+ }
}
private static int getStatusCodeExtra(Intent intent) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 4bb14a1..ba91d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -71,241 +71,241 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class HeadsUpCoordinatorTest : SysuiTestCase() {
- private lateinit var mCoordinator: HeadsUpCoordinator
+ private lateinit var coordinator: HeadsUpCoordinator
// captured listeners and pluggables:
- private lateinit var mCollectionListener: NotifCollectionListener
- private lateinit var mNotifPromoter: NotifPromoter
- private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender
- private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener
- private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
- private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
- private lateinit var mNotifSectioner: NotifSectioner
- private lateinit var mActionPressListener: Consumer<NotificationEntry>
+ private lateinit var collectionListener: NotifCollectionListener
+ private lateinit var notifPromoter: NotifPromoter
+ private lateinit var notifLifetimeExtender: NotifLifetimeExtender
+ private lateinit var beforeTransformGroupsListener: OnBeforeTransformGroupsListener
+ private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+ private lateinit var onHeadsUpChangedListener: OnHeadsUpChangedListener
+ private lateinit var notifSectioner: NotifSectioner
+ private lateinit var actionPressListener: Consumer<NotificationEntry>
- private val mNotifPipeline: NotifPipeline = mock()
- private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
- private val mHeadsUpManager: HeadsUpManager = mock()
- private val mHeadsUpViewBinder: HeadsUpViewBinder = mock()
- private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
- private val mRemoteInputManager: NotificationRemoteInputManager = mock()
- private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
- private val mHeaderController: NodeController = mock()
- private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
- private val mFlags: NotifPipelineFlags = mock()
+ private val notifPipeline: NotifPipeline = mock()
+ private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
+ private val headsUpManager: HeadsUpManager = mock()
+ private val headsUpViewBinder: HeadsUpViewBinder = mock()
+ private val notificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
+ private val remoteInputManager: NotificationRemoteInputManager = mock()
+ private val endLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
+ private val headerController: NodeController = mock()
+ private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
+ private val flags: NotifPipelineFlags = mock()
- private lateinit var mEntry: NotificationEntry
- private lateinit var mGroupSummary: NotificationEntry
- private lateinit var mGroupPriority: NotificationEntry
- private lateinit var mGroupSibling1: NotificationEntry
- private lateinit var mGroupSibling2: NotificationEntry
- private lateinit var mGroupChild1: NotificationEntry
- private lateinit var mGroupChild2: NotificationEntry
- private lateinit var mGroupChild3: NotificationEntry
- private val mSystemClock = FakeSystemClock()
- private val mExecutor = FakeExecutor(mSystemClock)
- private val mHuns: ArrayList<NotificationEntry> = ArrayList()
- private lateinit var mHelper: NotificationGroupTestHelper
+ private lateinit var entry: NotificationEntry
+ private lateinit var groupSummary: NotificationEntry
+ private lateinit var groupPriority: NotificationEntry
+ private lateinit var groupSibling1: NotificationEntry
+ private lateinit var groupSibling2: NotificationEntry
+ private lateinit var groupChild1: NotificationEntry
+ private lateinit var groupChild2: NotificationEntry
+ private lateinit var groupChild3: NotificationEntry
+ private val systemClock = FakeSystemClock()
+ private val executor = FakeExecutor(systemClock)
+ private val huns: ArrayList<NotificationEntry> = ArrayList()
+ private lateinit var helper: NotificationGroupTestHelper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- mHelper = NotificationGroupTestHelper(mContext)
- mCoordinator = HeadsUpCoordinator(
- mLogger,
- mSystemClock,
- mHeadsUpManager,
- mHeadsUpViewBinder,
- mNotificationInterruptStateProvider,
- mRemoteInputManager,
- mLaunchFullScreenIntentProvider,
- mFlags,
- mHeaderController,
- mExecutor)
- mCoordinator.attach(mNotifPipeline)
+ helper = NotificationGroupTestHelper(mContext)
+ coordinator = HeadsUpCoordinator(
+ logger,
+ systemClock,
+ headsUpManager,
+ headsUpViewBinder,
+ notificationInterruptStateProvider,
+ remoteInputManager,
+ launchFullScreenIntentProvider,
+ flags,
+ headerController,
+ executor)
+ coordinator.attach(notifPipeline)
// capture arguments:
- mCollectionListener = withArgCaptor {
- verify(mNotifPipeline).addCollectionListener(capture())
+ collectionListener = withArgCaptor {
+ verify(notifPipeline).addCollectionListener(capture())
}
- mNotifPromoter = withArgCaptor {
- verify(mNotifPipeline).addPromoter(capture())
+ notifPromoter = withArgCaptor {
+ verify(notifPipeline).addPromoter(capture())
}
- mNotifLifetimeExtender = withArgCaptor {
- verify(mNotifPipeline).addNotificationLifetimeExtender(capture())
+ notifLifetimeExtender = withArgCaptor {
+ verify(notifPipeline).addNotificationLifetimeExtender(capture())
}
- mBeforeTransformGroupsListener = withArgCaptor {
- verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture())
+ beforeTransformGroupsListener = withArgCaptor {
+ verify(notifPipeline).addOnBeforeTransformGroupsListener(capture())
}
- mBeforeFinalizeFilterListener = withArgCaptor {
- verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture())
+ beforeFinalizeFilterListener = withArgCaptor {
+ verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture())
}
- mOnHeadsUpChangedListener = withArgCaptor {
- verify(mHeadsUpManager).addListener(capture())
+ onHeadsUpChangedListener = withArgCaptor {
+ verify(headsUpManager).addListener(capture())
}
- mActionPressListener = withArgCaptor {
- verify(mRemoteInputManager).addActionPressListener(capture())
+ actionPressListener = withArgCaptor {
+ verify(remoteInputManager).addActionPressListener(capture())
}
- given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() }
- given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+ given(headsUpManager.allEntries).willAnswer { huns.stream() }
+ given(headsUpManager.isAlerting(anyString())).willAnswer { invocation ->
val key = invocation.getArgument<String>(0)
- mHuns.any { entry -> entry.key == key }
+ huns.any { entry -> entry.key == key }
}
- given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation ->
+ given(headsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation ->
val key = invocation.getArgument<String>(0)
- !mHuns.any { entry -> entry.key == key }
+ !huns.any { entry -> entry.key == key }
}
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- mNotifSectioner = mCoordinator.sectioner
- mNotifLifetimeExtender.setCallback(mEndLifetimeExtension)
- mEntry = NotificationEntryBuilder().build()
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ notifSectioner = coordinator.sectioner
+ notifLifetimeExtender.setCallback(endLifetimeExtension)
+ entry = NotificationEntryBuilder().build()
// Same summary we can use for either set of children
- mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500)
+ groupSummary = helper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500)
// One set of children with GROUP_ALERT_SUMMARY
- mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400)
- mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300)
- mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200)
+ groupPriority = helper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400)
+ groupSibling1 = helper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300)
+ groupSibling2 = helper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200)
// Another set of children with GROUP_ALERT_ALL
- mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
- mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
- mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
+ groupChild1 = helper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
+ groupChild2 = helper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
+ groupChild3 = helper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
// Set the default FSI decision
setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
// Run tests with default feature flag state
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default)
}
@Test
fun testCancelStickyNotification() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
- addHUN(mEntry)
- whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
- verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true))
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
+ verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true))
}
@Test
fun testCancelAndReAddStickyNotification() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
- addHUN(mEntry)
- whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true, false)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- addHUN(mEntry)
- assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true, false)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ addHUN(entry)
+ assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
}
@Test
fun hunNotRemovedWhenExtensionCancelled() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
- addHUN(mEntry)
- whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- mNotifLifetimeExtender.cancelLifetimeExtension(mEntry)
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), any())
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ notifLifetimeExtender.cancelLifetimeExtension(entry)
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(headsUpManager, times(0)).removeNotification(anyString(), any())
}
@Test
fun hunExtensionCancelledWhenHunActionPressed() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
- addHUN(mEntry)
- whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- mActionPressListener.accept(mEntry)
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- verify(mHeadsUpManager, times(1)).removeNotification(eq(mEntry.key), eq(true))
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ actionPressListener.accept(entry)
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(headsUpManager, times(1)).removeNotification(eq(entry.key), eq(true))
}
@Test
fun testCancelUpdatedStickyNotification() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
- addHUN(mEntry)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- addHUN(mEntry)
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ addHUN(entry)
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
}
@Test
fun testCancelNotification() {
- whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false)
- addHUN(mEntry)
- whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
- mExecutor.advanceClockToLast()
- mExecutor.runAllReady()
- verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false))
- verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(false)
+ addHUN(entry)
+ whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false))
+ verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
}
@Test
fun testOnEntryAdded_shouldFullScreen() {
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
- mCollectionListener.onEntryAdded(mEntry)
- verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
+ collectionListener.onEntryAdded(entry)
+ verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry)
}
@Test
fun testOnEntryAdded_shouldNotFullScreen() {
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
- mCollectionListener.onEntryAdded(mEntry)
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+ collectionListener.onEntryAdded(entry)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
}
@Test
fun testPromotesAddedHUN() {
// GIVEN the current entry should heads up
- whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+ setShouldHeadsUp(entry, true)
// WHEN the notification is added but not yet binding
- mCollectionListener.onEntryAdded(mEntry)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
+ collectionListener.onEntryAdded(entry)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any())
// THEN only promote mEntry
- assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ assertTrue(notifPromoter.shouldPromoteToTopLevel(entry))
}
@Test
fun testPromotesBindingHUN() {
// GIVEN the current entry should heads up
- whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+ setShouldHeadsUp(entry, true)
// WHEN the notification started binding on the previous run
- mCollectionListener.onEntryAdded(mEntry)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
- verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any())
+ collectionListener.onEntryAdded(entry)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
+ verify(headsUpViewBinder).bindHeadsUpView(eq(entry), any())
// THEN only promote mEntry
- assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ assertTrue(notifPromoter.shouldPromoteToTopLevel(entry))
}
@Test
fun testPromotesCurrentHUN() {
// GIVEN the current HUN is set to mEntry
- addHUN(mEntry)
+ addHUN(entry)
// THEN only promote the current HUN, mEntry
- assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
- assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder()
+ assertTrue(notifPromoter.shouldPromoteToTopLevel(entry))
+ assertFalse(notifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder()
.setPkg("test-package2")
.build()))
}
@@ -313,11 +313,11 @@
@Test
fun testIncludeInSectionCurrentHUN() {
// GIVEN the current HUN is set to mEntry
- addHUN(mEntry)
+ addHUN(entry)
// THEN only section the current HUN, mEntry
- assertTrue(mNotifSectioner.isInSection(mEntry))
- assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder()
+ assertTrue(notifSectioner.isInSection(entry))
+ assertFalse(notifSectioner.isInSection(NotificationEntryBuilder()
.setPkg("test-package")
.build()))
}
@@ -325,11 +325,11 @@
@Test
fun testLifetimeExtendsCurrentHUN() {
// GIVEN there is a HUN, mEntry
- addHUN(mEntry)
+ addHUN(entry)
// THEN only the current HUN, mEntry, should be lifetimeExtended
- assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0))
- assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* cancellationReason */ 0))
+ assertFalse(notifLifetimeExtender.maybeExtendLifetime(
NotificationEntryBuilder()
.setPkg("test-package")
.build(), /* cancellationReason */ 0))
@@ -338,726 +338,752 @@
@Test
fun testShowHUNOnInflationFinished() {
// WHEN a notification should HUN and its inflation is finished
- whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+ setShouldHeadsUp(entry, true)
- mCollectionListener.onEntryAdded(mEntry)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
- verify(mHeadsUpManager, never()).showNotification(mEntry)
+ collectionListener.onEntryAdded(entry)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
+ verify(headsUpManager, never()).showNotification(entry)
withArgCaptor<BindCallback> {
- verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture())
- }.onBindFinished(mEntry)
+ verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+ }.onBindFinished(entry)
// THEN we tell the HeadsUpManager to show the notification
- verify(mHeadsUpManager).showNotification(mEntry)
+ verify(headsUpManager).showNotification(entry)
}
@Test
fun testNoHUNOnInflationFinished() {
// WHEN a notification shouldn't HUN and its inflation is finished
- whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false)
- mCollectionListener.onEntryAdded(mEntry)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldHeadsUp(entry, false)
+ collectionListener.onEntryAdded(entry)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN we never bind the heads up view or tell HeadsUpManager to show the notification
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
- verify(mHeadsUpManager, never()).showNotification(mEntry)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any())
+ verify(headsUpManager, never()).showNotification(entry)
}
@Test
fun testOnEntryUpdated_toAlert() {
// GIVEN that an entry is posted that should not heads up
- setShouldHeadsUp(mEntry, false)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldHeadsUp(entry, false)
+ collectionListener.onEntryAdded(entry)
// WHEN it's updated to heads up
- setShouldHeadsUp(mEntry)
- mCollectionListener.onEntryUpdated(mEntry)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldHeadsUp(entry)
+ collectionListener.onEntryUpdated(entry)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification alerts
- finishBind(mEntry)
- verify(mHeadsUpManager).showNotification(mEntry)
+ finishBind(entry)
+ verify(headsUpManager).showNotification(entry)
}
@Test
fun testOnEntryUpdated_toNotAlert() {
// GIVEN that an entry is posted that should heads up
- setShouldHeadsUp(mEntry)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldHeadsUp(entry)
+ collectionListener.onEntryAdded(entry)
// WHEN it's updated to not heads up
- setShouldHeadsUp(mEntry, false)
- mCollectionListener.onEntryUpdated(mEntry)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldHeadsUp(entry, false)
+ collectionListener.onEntryUpdated(entry)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification is never bound or shown
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
}
@Test
fun testOnEntryRemovedRemovesHeadsUpNotification() {
// GIVEN the current HUN is mEntry
- addHUN(mEntry)
+ addHUN(entry)
// WHEN mEntry is removed from the notification collection
- mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0)
- whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false)
+ collectionListener.onEntryRemoved(entry, /* cancellation reason */ 0)
+ whenever(remoteInputManager.isSpinning(any())).thenReturn(false)
// THEN heads up manager should remove the entry
- verify(mHeadsUpManager).removeNotification(mEntry.key, false)
+ verify(headsUpManager).removeNotification(entry.key, false)
}
private fun addHUN(entry: NotificationEntry) {
- mHuns.add(entry)
- whenever(mHeadsUpManager.topEntry).thenReturn(entry)
- mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
- mNotifLifetimeExtender.cancelLifetimeExtension(entry)
+ huns.add(entry)
+ whenever(headsUpManager.topEntry).thenReturn(entry)
+ onHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
+ notifLifetimeExtender.cancelLifetimeExtension(entry)
}
@Test
fun testTransferIsolatedChildAlert_withGroupAlertSummary() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupSibling1))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupSibling1)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1))
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupSibling1)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupSibling1))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupSibling1))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupSibling1)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupSibling1)
// In addition make sure we have explicitly marked the summary as having interrupted due
// to the alert being transferred
- assertTrue(mGroupSummary.hasInterrupted())
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferIsolatedChildAlert_withGroupAlertAll() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupChild1))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupChild1)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1))
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupChild1)
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupChild1))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupChild1))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupChild1)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupChild1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupChild1)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupChild1)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() {
// WHEN a notification should HUN and its inflation is finished
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupSibling1)
- mCollectionListener.onEntryAdded(mGroupSibling2)
- val entryList = listOf(mGroupSibling1, mGroupSibling2)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupSibling1)
+ collectionListener.onEntryAdded(groupSibling2)
+ val entryList = listOf(groupSibling1, groupSibling2)
+ beforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupSibling1)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupSibling1)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
// THEN we tell the HeadsUpManager to show the notification
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() {
// WHEN a notification should HUN and its inflation is finished
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupChild1, groupChild2, groupPriority))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupChild1)
- mCollectionListener.onEntryAdded(mGroupChild2)
- val entryList = listOf(mGroupChild1, mGroupChild2)
- mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupChild1)
+ collectionListener.onEntryAdded(groupChild2)
+ val entryList = listOf(groupChild1, groupChild2)
+ beforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupChild1)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupChild1)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any())
// THEN we tell the HeadsUpManager to show the notification
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupChild1)
- verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupChild1)
+ verify(headsUpManager, never()).showNotification(groupChild2)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferToPriorityOnAddWithTwoSiblings() {
// WHEN a notification should HUN and its inflation is finished
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupPriority)
- mCollectionListener.onEntryAdded(mGroupSibling1)
- mCollectionListener.onEntryAdded(mGroupSibling2)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupPriority)
+ collectionListener.onEntryAdded(groupSibling1)
+ collectionListener.onEntryAdded(groupSibling2)
val beforeTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
val afterTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupSibling2))
.build()
- mBeforeFinalizeFilterListener
- .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+ beforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupPriority)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupPriority)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
// THEN we tell the HeadsUpManager to show the notification
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupPriority)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupPriority)
+ verify(headsUpManager, never()).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferToPriorityOnUpdateWithTwoSiblings() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority))
- mCollectionListener.onEntryUpdated(mGroupSummary)
- mCollectionListener.onEntryUpdated(mGroupPriority)
- mCollectionListener.onEntryUpdated(mGroupSibling1)
- mCollectionListener.onEntryUpdated(mGroupSibling2)
+ collectionListener.onEntryUpdated(groupSummary)
+ collectionListener.onEntryUpdated(groupPriority)
+ collectionListener.onEntryUpdated(groupSibling1)
+ collectionListener.onEntryUpdated(groupSibling2)
val beforeTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
val afterTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupSibling2))
.build()
- mBeforeFinalizeFilterListener
- .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+ beforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupPriority)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupPriority)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupPriority)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupPriority)
+ verify(headsUpManager, never()).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority))
- mCollectionListener.onEntryUpdated(mGroupSummary)
- mCollectionListener.onEntryUpdated(mGroupPriority)
+ collectionListener.onEntryUpdated(groupSummary)
+ collectionListener.onEntryUpdated(groupPriority)
val beforeTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
val afterTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupSibling2))
.build()
- mBeforeFinalizeFilterListener
- .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+ beforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupPriority)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupPriority)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupPriority)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
- assertTrue(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupPriority)
+ verify(headsUpManager, never()).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
+ assertTrue(groupSummary.hasInterrupted())
}
@Test
fun testNoTransferToPriorityOnUpdateOfTwoSiblings() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority))
- mCollectionListener.onEntryUpdated(mGroupSummary)
- mCollectionListener.onEntryUpdated(mGroupSibling1)
- mCollectionListener.onEntryUpdated(mGroupSibling2)
+ collectionListener.onEntryUpdated(groupSummary)
+ collectionListener.onEntryUpdated(groupSibling1)
+ collectionListener.onEntryUpdated(groupSibling2)
val beforeTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
val afterTransformGroup = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupSibling2))
.build()
- mBeforeFinalizeFilterListener
- .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+ beforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
- finishBind(mGroupSummary)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ finishBind(groupSummary)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
- verify(mHeadsUpManager).showNotification(mGroupSummary)
- verify(mHeadsUpManager, never()).showNotification(mGroupPriority)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ verify(headsUpManager).showNotification(groupSummary)
+ verify(headsUpManager, never()).showNotification(groupPriority)
+ verify(headsUpManager, never()).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
}
@Test
fun testNoTransferTwoChildAlert_withGroupAlertSummary() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupSibling1)
- mCollectionListener.onEntryAdded(mGroupSibling2)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupSibling1)
+ collectionListener.onEntryAdded(groupSibling2)
val groupEntry = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupSibling1, groupSibling2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
- finishBind(mGroupSummary)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+ finishBind(groupSummary)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any())
- verify(mHeadsUpManager).showNotification(mGroupSummary)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
- verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ verify(headsUpManager).showNotification(groupSummary)
+ verify(headsUpManager, never()).showNotification(groupSibling1)
+ verify(headsUpManager, never()).showNotification(groupSibling2)
}
@Test
fun testNoTransferTwoChildAlert_withGroupAlertAll() {
- setShouldHeadsUp(mGroupSummary)
- whenever(mNotifPipeline.allNotifs)
- .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2))
+ setShouldHeadsUp(groupSummary)
+ whenever(notifPipeline.allNotifs)
+ .thenReturn(listOf(groupSummary, groupChild1, groupChild2))
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupChild1)
- mCollectionListener.onEntryAdded(mGroupChild2)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupChild1)
+ collectionListener.onEntryAdded(groupChild2)
val groupEntry = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupChild1, groupChild2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
- finishBind(mGroupSummary)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+ finishBind(groupSummary)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild1), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any())
- verify(mHeadsUpManager).showNotification(mGroupSummary)
- verify(mHeadsUpManager, never()).showNotification(mGroupChild1)
- verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ verify(headsUpManager).showNotification(groupSummary)
+ verify(headsUpManager, never()).showNotification(groupChild1)
+ verify(headsUpManager, never()).showNotification(groupChild2)
}
@Test
fun testNoTransfer_groupSummaryNotAlerting() {
// When we have a group where the summary should not alert and exactly one child should
// alert, we should never mark the group summary as interrupted (because it doesn't).
- setShouldHeadsUp(mGroupSummary, false)
- setShouldHeadsUp(mGroupChild1, true)
- setShouldHeadsUp(mGroupChild2, false)
+ setShouldHeadsUp(groupSummary, false)
+ setShouldHeadsUp(groupChild1, true)
+ setShouldHeadsUp(groupChild2, false)
- mCollectionListener.onEntryAdded(mGroupSummary)
- mCollectionListener.onEntryAdded(mGroupChild1)
- mCollectionListener.onEntryAdded(mGroupChild2)
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(groupChild1)
+ collectionListener.onEntryAdded(groupChild2)
val groupEntry = GroupEntryBuilder()
- .setSummary(mGroupSummary)
- .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .setSummary(groupSummary)
+ .setChildren(listOf(groupChild1, groupChild2))
.build()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
- finishBind(mGroupChild1)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
+ finishBind(groupChild1)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any())
- verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
- verify(mHeadsUpManager).showNotification(mGroupChild1)
- verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
- assertFalse(mGroupSummary.hasInterrupted())
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager).showNotification(groupChild1)
+ verify(headsUpManager, never()).showNotification(groupChild2)
+ assertFalse(groupSummary.hasInterrupted())
}
@Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
// and is new enough to do so
- assertFalse(mEntry.hasInterrupted())
- mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ assertFalse(entry.hasInterrupted())
+ coordinator.setUpdateTime(entry, systemClock.currentTimeMillis())
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
// WHEN a ranking applied update occurs
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification is shown
- finishBind(mEntry)
- verify(mHeadsUpManager).showNotification(mEntry)
+ finishBind(entry)
+ verify(headsUpManager).showNotification(entry)
}
@Test
fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
// GIVEN that mEntry has alerted in the past, even if it's new
- mEntry.setInterruption()
- mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ entry.setInterruption()
+ coordinator.setUpdateTime(entry, systemClock.currentTimeMillis())
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
// WHEN a ranking applied update occurs
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification is never bound or shown
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
}
@Test
fun testOnRankingApplied_entryUpdatedToHun() {
// GIVEN that mEntry is added in a state where it should not HUN
- setShouldHeadsUp(mEntry, false)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldHeadsUp(entry, false)
+ collectionListener.onEntryAdded(entry)
// and it is then updated such that it should now HUN
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
// WHEN a ranking applied update occurs
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification is shown
- finishBind(mEntry)
- verify(mHeadsUpManager).showNotification(mEntry)
+ finishBind(entry)
+ verify(headsUpManager).showNotification(entry)
}
@Test
fun testOnRankingApplied_entryUpdatedButTooOld() {
// GIVEN that mEntry is added in a state where it should not HUN
- setShouldHeadsUp(mEntry, false)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldHeadsUp(entry, false)
+ collectionListener.onEntryAdded(entry)
// and it was actually added 10s ago
- mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000)
+ coordinator.setUpdateTime(entry, systemClock.currentTimeMillis() - 10000)
// WHEN it is updated to HUN and then a ranking update occurs
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN the notification is never bound or shown
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
}
@Test
fun onEntryAdded_whenLaunchingFSI_doesLogDecision() {
// GIVEN A new notification can FSI
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ collectionListener.onEntryAdded(entry)
- verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
- mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry)
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+ )
}
@Test
fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() {
// GIVEN A new notification can't FSI
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+ collectionListener.onEntryAdded(entry)
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
- mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verifyLoggedFullScreenIntentDecision(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
}
@Test
fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() {
// GIVEN A new notification can't FSI because of DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
- mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+ )
}
@Test
fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
// Ensure the feature flag is off
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(false)
// GIVEN that mEntry was previously suppressed from full-screen only by DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
// Verify that this causes a log
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
- mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- clearInvocations(mNotificationInterruptStateProvider)
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+ )
+ clearInterruptionProviderInvocations()
// and it is then updated to allow full screen
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
- mCollectionListener.onRankingApplied()
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
+ collectionListener.onRankingApplied()
// THEN it should not full screen because the feature is off
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
// VERIFY that no additional logging happens either
- verify(mNotificationInterruptStateProvider, never())
- .logFullScreenIntentDecision(any(), any())
+ verifyNoFullScreenIntentDecisionLogged()
}
@Test
fun testOnRankingApplied_updateToFullScreen() {
// Turn on the feature
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(true)
// GIVEN that mEntry was previously suppressed from full-screen only by DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
// at this point, it should not have full screened, but should have logged
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
- FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- clearInvocations(mNotificationInterruptStateProvider)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+ )
+ clearInterruptionProviderInvocations()
// and it is then updated to allow full screen AND HUN
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN it should full screen and log but it should NOT HUN
- verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
- FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- clearInvocations(mNotificationInterruptStateProvider)
+ verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+ )
+ clearInterruptionProviderInvocations()
// WHEN ranking updates again and the pipeline reruns
- clearInvocations(mLaunchFullScreenIntentProvider)
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ clearInvocations(launchFullScreenIntentProvider)
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// VERIFY that the FSI does not launch again or log
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mNotificationInterruptStateProvider, never())
- .logFullScreenIntentDecision(any(), any())
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verifyNoFullScreenIntentDecisionLogged()
}
@Test
fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() {
// Turn on the feature
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(true)
// GIVEN that mEntry was previously suppressed from full-screen only by DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
// at this point, it should not have full screened, but should have logged
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
- FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- clearInvocations(mNotificationInterruptStateProvider)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+ )
+ clearInterruptionProviderInvocations()
// ranking is applied with only DND blocking FSI
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN it should still not yet full screen or HUN
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
// Same decision as before; is not logged
- verify(mNotificationInterruptStateProvider, never())
- .logFullScreenIntentDecision(any(), any())
- clearInvocations(mNotificationInterruptStateProvider)
+ verifyNoFullScreenIntentDecisionLogged()
+ clearInterruptionProviderInvocations()
// and it is then updated to allow full screen AND HUN
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- setShouldHeadsUp(mEntry)
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(entry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN it should full screen and log but it should NOT HUN
- verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
- verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
- FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- clearInvocations(mNotificationInterruptStateProvider)
+ verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry)
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
+ verifyLoggedFullScreenIntentDecision(
+ entry,
+ FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+ )
+ clearInterruptionProviderInvocations()
}
@Test
fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() {
// Turn on the feature
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(true)
// GIVEN that mEntry was previously suppressed from full-screen only by DND
- whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(entry))
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
// at this point, it should not have full screened
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry)
// now some other condition blocks FSI in addition to DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// THEN it should NOT full screen or HUN
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
- verify(mHeadsUpManager, never()).showNotification(any())
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(headsUpManager, never()).showNotification(any())
// NOW the DND logic changes and FSI and HUN are available
- clearInvocations(mLaunchFullScreenIntentProvider)
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- setShouldHeadsUp(mEntry)
- mCollectionListener.onRankingApplied()
- mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
- mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ clearInvocations(launchFullScreenIntentProvider)
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(entry)
+ collectionListener.onRankingApplied()
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
// VERIFY that the FSI didn't happen, but that we do HUN
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
- finishBind(mEntry)
- verify(mHeadsUpManager).showNotification(mEntry)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ finishBind(entry)
+ verify(headsUpManager).showNotification(entry)
}
@Test
fun testOnRankingApplied_noFSIWhenAlsoSuppressedForOtherReasons() {
// Feature on
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(true)
// GIVEN that mEntry is suppressed by DND (functionally), but not *only* DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+ collectionListener.onEntryAdded(entry)
// and it is updated to full screen later
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
- mCollectionListener.onRankingApplied()
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ collectionListener.onRankingApplied()
// THEN it should still not full screen because something else was blocking it before
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry)
}
@Test
fun testOnRankingApplied_noFSIWhenTooOld() {
// Feature on
- whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+ whenever(flags.fsiOnDNDUpdate()).thenReturn(true)
// GIVEN that mEntry is suppressed only by DND
- setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
- mCollectionListener.onEntryAdded(mEntry)
+ setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ collectionListener.onEntryAdded(entry)
// but it's >10s old
- mCoordinator.addForFSIReconsideration(mEntry, mSystemClock.currentTimeMillis() - 10000)
+ coordinator.addForFSIReconsideration(entry, systemClock.currentTimeMillis() - 10000)
// and it is updated to full screen later
- setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
- mCollectionListener.onRankingApplied()
+ setShouldFullScreen(entry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
+ collectionListener.onRankingApplied()
// THEN it should still not full screen because it's too old
- verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry)
}
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
- whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
- whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
+ whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
+ whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
.thenReturn(should)
}
private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
- whenever(mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry))
+ whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry))
.thenReturn(decision)
}
+ private fun verifyLoggedFullScreenIntentDecision(
+ entry: NotificationEntry,
+ decision: FullScreenIntentDecision
+ ) {
+ verify(notificationInterruptStateProvider).logFullScreenIntentDecision(entry, decision)
+ }
+
+ private fun verifyNoFullScreenIntentDecisionLogged() {
+ verify(notificationInterruptStateProvider, never())
+ .logFullScreenIntentDecision(any(), any())
+ }
+
+ private fun clearInterruptionProviderInvocations() {
+ clearInvocations(notificationInterruptStateProvider)
+ }
+
private fun finishBind(entry: NotificationEntry) {
- verify(mHeadsUpManager, never()).showNotification(entry)
+ verify(headsUpManager, never()).showNotification(entry)
withArgCaptor<BindCallback> {
- verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+ verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
}.onBindFinished(entry)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 68d67ca..89f8bdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -89,7 +89,7 @@
@Mock
private KeyguardViewMediator mKeyguardViewMediator;
@Mock
- private BiometricUnlockController.BiometricModeListener mBiometricModeListener;
+ private BiometricUnlockController.BiometricUnlockEventsListener mBiometricUnlockEventsListener;
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
@@ -145,7 +145,7 @@
mSystemClock
);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
+ mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 44fbd5b..6306a36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -29,6 +30,7 @@
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
+ override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID)
override val isEmergencyOnly = MutableStateFlow(false)
override val isRoaming = MutableStateFlow(false)
override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index f9c72d5..3591c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -67,6 +67,8 @@
override val mobileIsDefault = MutableStateFlow(false)
+ override val hasCarrierMergedConnection = MutableStateFlow(false)
+
override val defaultConnectionIsValidated = MutableStateFlow(false)
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index f2bb66a..423c476 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -593,7 +593,6 @@
val realRepo =
MobileConnectionRepositoryImpl(
- context,
SUB_ID,
defaultNetworkName = NetworkNameModel.Default("default"),
networkNameSeparator = SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 934e1c6..d1df6e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -42,6 +42,7 @@
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
@@ -116,7 +117,6 @@
underTest =
MobileConnectionRepositoryImpl(
- context,
SUB_1_ID,
DEFAULT_NAME,
SEP,
@@ -359,6 +359,36 @@
}
@Test
+ fun carrierId_initialValueCaptured() =
+ testScope.runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ var latest: Int? = null
+ val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(1234)
+
+ job.cancel()
+ }
+
+ @Test
+ fun carrierId_updatesOnBroadcast() =
+ testScope.runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ var latest: Int? = null
+ val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, carrierIdIntent(carrierId = 4321))
+ }
+
+ assertThat(latest).isEqualTo(4321)
+
+ job.cancel()
+ }
+
+ @Test
fun carrierNetworkChange() =
testScope.runTest {
var latest: Boolean? = null
@@ -796,6 +826,15 @@
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
+ private fun carrierIdIntent(
+ subId: Int = SUB_1_ID,
+ carrierId: Int,
+ ): Intent =
+ Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_CARRIER_ID, carrierId)
+ }
+
private fun spnIntent(
subId: Int = SUB_1_ID,
showSpn: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 9da9ff7..4f15aed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -117,7 +117,6 @@
underTest =
MobileConnectionRepositoryImpl(
- context,
SUB_1_ID,
DEFAULT_NAME,
SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index ddff17ae..7cc59b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -68,6 +68,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
@@ -155,7 +156,6 @@
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
- context = context,
telephonyManager = telephonyManager,
bgDispatcher = IMMEDIATE,
logger = logger,
@@ -767,28 +767,6 @@
job.cancel()
}
- /** Regression test for b/272586234. */
- @Test
- fun mobileIsDefault_carrierMergedViaWifi_isDefault() =
- runBlocking(IMMEDIATE) {
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val caps =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
@Test
fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
runBlocking(IMMEDIATE) {
@@ -810,49 +788,6 @@
job.cancel()
}
- /** Regression test for b/272586234. */
- @Test
- fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() =
- runBlocking(IMMEDIATE) {
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val caps =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
-
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() =
- runBlocking(IMMEDIATE) {
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val caps =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
-
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
@Test
fun mobileIsDefault_wifiDefault_mobileNotDefault() =
runBlocking(IMMEDIATE) {
@@ -889,6 +824,195 @@
job.cancel()
}
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ yield()
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ yield()
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ // WHEN the default callback isn't carrier merged
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+ yield()
+
+ // THEN hasCarrierMergedConnection is true
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
@Test
fun defaultConnectionIsValidated_startsAsFalse() {
assertThat(underTest.defaultConnectionIsValidated.value).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 8d2c569..f054422e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -17,11 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength
-import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,8 +42,10 @@
override val mobileIsDefault = MutableStateFlow(true)
- private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
- override val networkTypeIconGroup = _iconGroup
+ override val networkTypeIconGroup =
+ MutableStateFlow<NetworkTypeIconModel>(
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ )
override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
@@ -73,10 +75,6 @@
override val isForceHidden = MutableStateFlow(false)
- fun setIconGroup(group: SignalIcon.MobileIconGroup) {
- _iconGroup.value = group
- }
-
fun setIsEmergencyOnly(emergency: Boolean) {
_isEmergencyOnly.value = emergency
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index d6fdad4..3ced7b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -59,7 +59,6 @@
override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val alwaysUseCdmaLevel = MutableStateFlow(false)
- override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
override val mobileIsDefault = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 2054e8b..8d7f0f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -19,7 +19,8 @@
import android.telephony.CellSignalStrength
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
-import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -31,18 +32,24 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
@@ -50,29 +57,17 @@
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
- private val scope = CoroutineScope(IMMEDIATE)
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
- underTest =
- MobileIconInteractorImpl(
- scope,
- mobileIconsInteractor.activeDataConnectionHasDataEnabled,
- mobileIconsInteractor.alwaysShowDataRatIcon,
- mobileIconsInteractor.alwaysUseCdmaLevel,
- mobileIconsInteractor.mobileIsDefault,
- mobileIconsInteractor.defaultMobileIconMapping,
- mobileIconsInteractor.defaultMobileIconGroup,
- mobileIconsInteractor.defaultDataSubId,
- mobileIconsInteractor.isDefaultConnectionFailed,
- mobileIconsInteractor.isForceHidden,
- connectionRepository,
- )
+ underTest = createInteractor()
}
@Test
fun gsm_level_default_unknown() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = true
var latest: Int? = null
@@ -85,7 +80,7 @@
@Test
fun gsm_usesGsmLevel() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = true
connectionRepository.primaryLevel.value = GSM_LEVEL
connectionRepository.cdmaLevel.value = CDMA_LEVEL
@@ -100,7 +95,7 @@
@Test
fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = true
connectionRepository.primaryLevel.value = GSM_LEVEL
connectionRepository.cdmaLevel.value = CDMA_LEVEL
@@ -116,7 +111,7 @@
@Test
fun notGsm_level_default_unknown() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = false
var latest: Int? = null
@@ -128,7 +123,7 @@
@Test
fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = false
connectionRepository.primaryLevel.value = GSM_LEVEL
connectionRepository.cdmaLevel.value = CDMA_LEVEL
@@ -144,7 +139,7 @@
@Test
fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.isGsm.value = false
connectionRepository.primaryLevel.value = GSM_LEVEL
connectionRepository.cdmaLevel.value = CDMA_LEVEL
@@ -160,7 +155,7 @@
@Test
fun numberOfLevels_comesFromRepo() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
@@ -175,101 +170,106 @@
@Test
fun iconGroup_three_g() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.resolvedNetworkType.value =
DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
job.cancel()
}
@Test
fun iconGroup_updates_on_change() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.resolvedNetworkType.value =
DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
connectionRepository.resolvedNetworkType.value =
DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
- yield()
- assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
job.cancel()
}
@Test
fun iconGroup_5g_override_type() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.resolvedNetworkType.value =
OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(TelephonyIcons.NR_5G)
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
job.cancel()
}
@Test
fun iconGroup_default_if_no_lookup() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.resolvedNetworkType.value =
DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON)
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
job.cancel()
}
@Test
fun iconGroup_carrierMerged_usesOverride() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(CarrierMergedNetworkType.iconGroupOverride)
+ assertThat(latest)
+ .isEqualTo(
+ NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride)
+ )
job.cancel()
}
@Test
- fun `icon group - checks default data`() =
- runBlocking(IMMEDIATE) {
- mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
+ fun overrideIcon_usesCarrierIdOverride() =
+ testScope.runTest {
+ val overrides =
+ mock<MobileIconCarrierIdOverrides>().also {
+ whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true)
+ whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234)
+ }
+
+ underTest = createInteractor(overrides)
+
connectionRepository.resolvedNetworkType.value =
DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- var latest: MobileIconGroup? = null
+ var latest: NetworkTypeIconModel? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
-
- // Default data sub id changes to something else
- mobileIconsInteractor.defaultDataSubId.value = 123
- yield()
-
- assertThat(latest).isEqualTo(TelephonyIcons.NOT_DEFAULT_DATA)
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_matchesParent() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
@@ -284,7 +284,7 @@
@Test
fun alwaysUseCdmaLevel_matchesParent() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
@@ -299,7 +299,7 @@
@Test
fun test_isDefaultDataEnabled_matchesParent() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
@@ -314,7 +314,7 @@
@Test
fun test_isDefaultConnectionFailed_matchedParent() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isDefaultConnectionFailed.launchIn(this)
mobileIconsInteractor.isDefaultConnectionFailed.value = false
@@ -328,12 +328,11 @@
@Test
fun dataState_connected() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
connectionRepository.dataConnectionState.value = DataConnectionState.Connected
- yield()
assertThat(latest).isTrue()
@@ -342,7 +341,7 @@
@Test
fun dataState_notConnected() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
@@ -355,7 +354,7 @@
@Test
fun `isInService - uses repository value`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
@@ -372,19 +371,17 @@
@Test
fun `roaming - is gsm - uses connection model`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
connectionRepository.isGsm.value = true
connectionRepository.isRoaming.value = false
- yield()
assertThat(latest).isFalse()
connectionRepository.isRoaming.value = true
- yield()
assertThat(latest).isTrue()
@@ -393,21 +390,19 @@
@Test
fun `roaming - is cdma - uses cdma roaming bit`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = false
connectionRepository.isGsm.value = false
connectionRepository.isRoaming.value = true
- yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
connectionRepository.isGsm.value = false
connectionRepository.isRoaming.value = false
- yield()
assertThat(latest).isTrue()
@@ -416,7 +411,7 @@
@Test
fun `roaming - false while carrierNetworkChangeActive`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
@@ -424,13 +419,11 @@
connectionRepository.isGsm.value = false
connectionRepository.isRoaming.value = true
connectionRepository.carrierNetworkChangeActive.value = true
- yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
connectionRepository.isGsm.value = true
- yield()
assertThat(latest).isFalse()
@@ -439,7 +432,7 @@
@Test
fun `network name - uses operatorAlphaShot when non null and repo is default`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -448,20 +441,17 @@
// Default network name, operator name is non-null, uses the operator name
connectionRepository.networkName.value = DEFAULT_NAME
connectionRepository.operatorAlphaShort.value = testOperatorName
- yield()
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
// Default network name, operator name is null, uses the default
connectionRepository.operatorAlphaShort.value = null
- yield()
assertThat(latest).isEqualTo(DEFAULT_NAME)
// Derived network name, operator name non-null, uses the derived name
connectionRepository.networkName.value = DERIVED_NAME
connectionRepository.operatorAlphaShort.value = testOperatorName
- yield()
assertThat(latest).isEqualTo(DERIVED_NAME)
@@ -470,7 +460,7 @@
@Test
fun isForceHidden_matchesParent() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
@@ -483,9 +473,25 @@
job.cancel()
}
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
+ private fun createInteractor(
+ overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+ ) =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractor.alwaysShowDataRatIcon,
+ mobileIconsInteractor.alwaysUseCdmaLevel,
+ mobileIconsInteractor.mobileIsDefault,
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
+ mobileIconsInteractor.isForceHidden,
+ connectionRepository,
+ context,
+ overrides,
+ )
+ companion object {
private const val GSM_LEVEL = 1
private const val CDMA_LEVEL = 2
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 898e897..dc68386 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
@@ -88,6 +88,7 @@
connectivityRepository,
userSetupRepository,
testScope.backgroundScope,
+ context,
)
}
@@ -455,7 +456,50 @@
}
@Test
- fun mobileIsDefault_usesRepoValue() =
+ fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.hasCarrierMergedConnection.value = true
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_updatesWhenRepoUpdates() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
@@ -466,7 +510,7 @@
connectionsRepository.mobileIsDefault.value = false
assertThat(latest).isFalse()
- connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.hasCarrierMergedConnection.value = true
assertThat(latest).isTrue()
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index a6d9152..e99be86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -71,9 +72,9 @@
setLevel(1)
setIsDefaultDataEnabled(true)
setIsFailedConnection(false)
- setIconGroup(TelephonyIcons.THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
+ networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
isDataConnected.value = true
}
commonImpl =
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 8ea8f87..1b6ab4d 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
@@ -20,6 +20,7 @@
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
+import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -27,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -76,9 +78,9 @@
setLevel(1)
setIsDefaultDataEnabled(true)
setIsFailedConnection(false)
- setIconGroup(THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
isDataConnected.value = true
}
createAndSetViewModel()
@@ -255,7 +257,7 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -266,10 +268,11 @@
}
@Test
- fun networkType_nullWhenDisabled() =
+ fun networkType_null_whenDisabled() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.setIsDataEnabled(false)
+ interactor.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -279,15 +282,21 @@
}
@Test
- fun networkType_nullWhenFailedConnection() =
+ fun networkTypeIcon_notNull_whenEnabled() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.setIsDataEnabled(true)
- interactor.setIsFailedConnection(true)
+ interactor.isDataConnected.value = true
+ interactor.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -301,11 +310,11 @@
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
assertThat(latest).isEqualTo(initial)
interactor.isDataConnected.value = false
@@ -324,7 +333,7 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.setIsDataEnabled(true)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -342,8 +351,8 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
- interactor.setIsDataEnabled(true)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ interactor.setIsDataEnabled(false)
interactor.alwaysShowDataRatIcon.value = true
var latest: Icon? = null
@@ -362,7 +371,7 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.isDataConnected.value = false
interactor.alwaysShowDataRatIcon.value = true
@@ -382,7 +391,7 @@
@Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.setIsFailedConnection(true)
interactor.alwaysShowDataRatIcon.value = true
@@ -400,9 +409,24 @@
}
@Test
+ fun networkType_alwaysShow_notShownWhenInvalidDataTypeIcon() =
+ testScope.runTest {
+ // The UNKNOWN icon group doesn't have a valid data type icon ID
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(UNKNOWN)
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun `network type - alwaysShow - shown when not default`() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.mobileIsDefault.value = false
interactor.alwaysShowDataRatIcon.value = true
@@ -422,7 +446,7 @@
@Test
fun `network type - not shown when not default`() =
testScope.runTest {
- interactor.setIconGroup(THREE_G)
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
interactor.isDataConnected.value = true
interactor.mobileIsDefault.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 87d4f5c..661002d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -491,6 +492,111 @@
}
@Test
+ fun defaultConnections_nullUnderlyingInfo_noError() {
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(null)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ // No assert, just verify no error
+ }
+
+ @Test
+ fun defaultConnections_underlyingInfoHasNullCapabilities_noError() {
+ val underlyingNetworkWithNull = mock<Network>()
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetworkWithNull))
+ .thenReturn(null)
+
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetworkWithNull))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ // No assert, just verify no error
+ }
+
+ // This test verifies our internal API for completeness, but we don't expect this case to ever
+ // happen in practice.
+ @Test
+ fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() =
+ testScope.runTest {
+ var latest: DefaultConnectionModel? = null
+ val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ assertThat(latest!!.mobile.isDefault).isTrue()
+ assertThat(latest!!.carrierMerged.isDefault).isTrue()
+ assertThat(latest!!.wifi.isDefault).isTrue()
+
+ job.cancel()
+ }
+
+ /** Test for b/225902574. */
+ @Test
+ fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() =
+ testScope.runTest {
+ var latest: DefaultConnectionModel? = null
+ val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ assertThat(latest!!.mobile.isDefault).isTrue()
+ assertThat(latest!!.carrierMerged.isDefault).isTrue()
+ assertThat(latest!!.wifi.isDefault).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun defaultConnections_multipleTransports_multipleDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
@@ -548,6 +654,279 @@
job.cancel()
}
+ @Test
+ fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val vcnInfo = VcnTransportInfo(wifiInfo)
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(vcnInfo)
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellularOrWifiTransport_noInfo() {
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(mock<WifiInfo>())
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingWifi_hasInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingWifiInfo = mock<WifiInfo>()
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we fetch the underlying wifi info
+ assertThat(result).isEqualTo(underlyingWifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingWifiInfo = mock<WifiInfo>()
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network but is *not* CELLULAR
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we DON'T fetch the underlying wifi info
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val underlyingNetwork = mock<Network>()
+ val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying VCN network with wifi
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we fetch the wifi info
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network but it is *not* CELLULAR
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we DON'T fetch the underlying wifi info
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingCellularWithCarrierMerged_hasInfo() {
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(carrierMergedInfo)
+ assertThat(result!!.isCarrierMerged).isTrue()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_multipleUnderlying_usesFirstNonNull() {
+ // First underlying: Not wifi
+ val underlyingNotWifiNetwork = mock<Network>()
+ val underlyingNotWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNotWifiNetwork))
+ .thenReturn(underlyingNotWifiCapabilities)
+
+ // Second underlying: wifi
+ val underlyingWifiNetwork1 = mock<Network>()
+ val underlyingWifiInfo1 = mock<WifiInfo>()
+ val underlyingWifiCapabilities1 =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo1)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork1))
+ .thenReturn(underlyingWifiCapabilities1)
+
+ // Third underlying: also wifi
+ val underlyingWifiNetwork2 = mock<Network>()
+ val underlyingWifiInfo2 = mock<WifiInfo>()
+ val underlyingWifiCapabilities2 =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo2)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork2))
+ .thenReturn(underlyingWifiCapabilities2)
+
+ // WHEN the main capabilities has multiple underlying networks
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(
+ listOf(
+ underlyingNotWifiNetwork,
+ underlyingWifiNetwork1,
+ underlyingWifiNetwork2,
+ )
+ )
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN the first wifi one is used
+ assertThat(result).isEqualTo(underlyingWifiInfo1)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_nestedUnderlying_doesNotLookAtNested() {
+ // WHEN there are two layers of underlying networks...
+
+ // Nested network
+ val nestedUnderlyingNetwork = mock<Network>()
+ val nestedWifiInfo = mock<WifiInfo>()
+ val nestedCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(nestedWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(nestedUnderlyingNetwork))
+ .thenReturn(nestedCapabilities)
+
+ // Underlying network containing the nested network
+ val underlyingNetwork = mock<Network>()
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(nestedUnderlyingNetwork))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network containing the underlying network, which contains the nested network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN only the first layer is checked, and the first layer has no wifi info
+ assertThat(result).isNull()
+ }
+
private fun createAndSetRepo() {
underTest =
ConnectivityRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index f69e9a3..ddc6d48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -400,7 +400,7 @@
}
@Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
+ fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
@@ -437,6 +437,75 @@
}
@Test
+ fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ }
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged, so wifi is default
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged, so wifi is default
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun isWifiDefault_wifiNetworkLost_isFalse() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
@@ -511,6 +580,81 @@
}
@Test
+ fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index f9bfafc..766a5ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -29,12 +29,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
import android.os.IBinder;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -44,6 +47,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.Objects;
@SmallTest
@@ -93,15 +97,22 @@
FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private FakeUserTracker mUserTracker;
+
+ private static final int MAIN_USER_ID = 10;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
+ // Set the main user as the current user.
+ mUserTracker.set(List.of(new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)), 0);
}
@Test
public void testConnect() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
- mIntent, mExecutor, mTransformer);
+ mIntent, mUserTracker, mExecutor, mTransformer);
// Register twice to ensure only one callback occurs.
connection.addCallback(mCallback);
connection.addCallback(mCallback);
@@ -121,15 +132,16 @@
@Test
public void testDisconnect() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
- mIntent, mExecutor, mTransformer);
+ mIntent, mUserTracker, mExecutor, mTransformer);
connection.addCallback(mCallback);
connection.onServiceDisconnected(mComponentName);
+ mExecutor.runAllReady();
// Disconnects before binds should be ignored.
verify(mCallback, never()).onDisconnected(eq(connection), anyInt());
- when(mContext.bindService(eq(mIntent), anyInt(), eq(mExecutor), eq(connection)))
- .thenReturn(true);
+ when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
+ eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
connection.onServiceDisconnected(mComponentName);
@@ -151,15 +163,16 @@
@Test
public void testUnbind() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
- mIntent, mExecutor, mTransformer);
+ mIntent, mUserTracker, mExecutor, mTransformer);
connection.addCallback(mCallback);
connection.onServiceDisconnected(mComponentName);
+ mExecutor.runAllReady();
// Disconnects before binds should be ignored.
verify(mCallback, never()).onDisconnected(eq(connection), anyInt());
- when(mContext.bindService(eq(mIntent), anyInt(), eq(mExecutor), eq(connection)))
- .thenReturn(true);
+ when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
+ eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
connection.bind();
mExecutor.runAllReady();
@@ -175,10 +188,11 @@
@Test
public void testBindServiceThrowsError() {
ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
- mIntent, mExecutor, mTransformer);
+ mIntent, mUserTracker, mExecutor, mTransformer);
connection.addCallback(mCallback);
- when(mContext.bindService(eq(mIntent), anyInt(), eq(mExecutor), eq(connection)))
+ when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
+ eq(UserHandle.of(MAIN_USER_ID))))
.thenThrow(new SecurityException());
// Verify that the exception was caught and that bind returns false, and we properly
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 28bdca9..e824565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -85,6 +85,9 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -127,11 +130,9 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleBadgeIconFactory;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleIconFactory;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
@@ -1225,8 +1226,13 @@
BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
mBubbleController,
mBubbleController.getStackView(),
- new BubbleIconFactory(mContext),
- new BubbleBadgeIconFactory(mContext),
+ new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size)),
+ new BubbleBadgeIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width)),
bubble,
true /* skipInflation */);
verify(userContext, times(1)).getPackageManager();
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
index 380c1fc..156d2fc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -25,14 +25,18 @@
* It could be used when no activity context is available
* TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
*/
-class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
+class ScreenSizeFoldProvider(context: Context) : FoldProvider {
+ private var isFolded: Boolean = false
private var callbacks: MutableList<FoldCallback> = arrayListOf()
- private var lastWidth: Int = 0
+
+ init {
+ onConfigurationChange(context.resources.configuration)
+ }
override fun registerCallback(callback: FoldCallback, executor: Executor) {
callbacks += callback
- onConfigurationChange(context.resources.configuration)
+ callback.onFoldUpdated(isFolded)
}
override fun unregisterCallback(callback: FoldCallback) {
@@ -40,16 +44,14 @@
}
fun onConfigurationChange(newConfig: Configuration) {
- if (lastWidth == newConfig.smallestScreenWidthDp) {
+ val newIsFolded =
+ newConfig.smallestScreenWidthDp < INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+ if (newIsFolded == isFolded) {
return
}
- if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
- callbacks.forEach { it.onFoldUpdated(false) }
- } else {
- callbacks.forEach { it.onFoldUpdated(true) }
- }
- lastWidth = newConfig.smallestScreenWidthDp
+ isFolded = newIsFolded
+ callbacks.forEach { it.onFoldUpdated(isFolded) }
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index a633a5e..46001a7 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -256,6 +256,12 @@
}
}
+ override fun markScreenAsTurnedOn() {
+ if (!isFolded) {
+ isUnfoldHandled = true
+ }
+ }
+
override fun onScreenTurningOn() {
isScreenOn = true
updateHingeAngleProviderState()
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index f09b53d..2ee2940 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -35,5 +35,12 @@
* Called when the screen is starting to be turned on.
*/
fun onScreenTurningOn()
+
+ /**
+ * Called when the screen is already turned on but it happened before the creation
+ * of the unfold progress provider, so we won't play the actual animation but we treat
+ * the current state of the screen as 'turned on'
+ */
+ fun markScreenAsTurnedOn()
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 0e25a06..baed181 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -201,7 +201,8 @@
private List<AccessibilityWindowInfo> mWindows;
private boolean mTrackingWindows = false;
private boolean mHasWatchOutsideTouchWindow;
- private int mProxyDisplayAccessibilityFocusedWindow;
+ private int mProxyDisplayAccessibilityFocusedWindow =
+ AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
private boolean mIsProxy;
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 2188b99..2f3e4c0 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -24,6 +24,7 @@
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -34,6 +35,7 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.util.DumpUtils;
+import com.android.server.utils.Slogf;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -51,12 +53,8 @@
private UiAutomationService mUiAutomationService;
- private AccessibilityServiceInfo mUiAutomationServiceInfo;
-
private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
- private AccessibilityTrace mTrace;
-
private int mUiAutomationFlags;
UiAutomationManager(Object lock) {
@@ -97,9 +95,10 @@
WindowManagerInternal windowManagerInternal,
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager awm, int flags) {
+ accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
+ Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d",
+ accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier());
synchronized (mLock) {
- accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
-
if (mUiAutomationService != null) {
throw new IllegalStateException(
"UiAutomationService " + mUiAutomationService.mServiceInterface
@@ -116,7 +115,6 @@
mUiAutomationFlags = flags;
mSystemSupport = systemSupport;
- mTrace = trace;
// Ignore registering UiAutomation if it is not allowed to use the accessibility
// subsystem.
if (!useAccessibility()) {
@@ -126,7 +124,6 @@
mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
systemActionPerformer, awm);
mUiAutomationServiceOwner = owner;
- mUiAutomationServiceInfo = accessibilityServiceInfo;
mUiAutomationService.mServiceInterface = serviceClient;
try {
mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
diff --git a/services/art-profile b/services/art-profile
index 64c7f3d..95ed8e3 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -9213,7 +9213,7 @@
HPLcom/android/server/power/hint/HintManagerService$AppHintSession;->close()V
HSPLcom/android/server/power/hint/HintManagerService$AppHintSession;->reportActualWorkDuration([J[J)V+]Lcom/android/server/power/hint/HintManagerService$NativeWrapper;Lcom/android/server/power/hint/HintManagerService$NativeWrapper;]Lcom/android/server/power/hint/HintManagerService$AppHintSession;Lcom/android/server/power/hint/HintManagerService$AppHintSession;
HSPLcom/android/server/power/hint/HintManagerService$AppHintSession;->sendHint(I)V+]Lcom/android/server/power/hint/HintManagerService$NativeWrapper;Lcom/android/server/power/hint/HintManagerService$NativeWrapper;]Lcom/android/server/power/hint/HintManagerService$AppHintSession;Lcom/android/server/power/hint/HintManagerService$AppHintSession;
-HSPLcom/android/server/power/hint/HintManagerService$AppHintSession;->updateHintAllowed()Z+]Lcom/android/server/power/hint/HintManagerService$UidObserver;Lcom/android/server/power/hint/HintManagerService$UidObserver;]Lcom/android/server/power/hint/HintManagerService$AppHintSession;Lcom/android/server/power/hint/HintManagerService$AppHintSession;
+HSPLcom/android/server/power/hint/HintManagerService$AppHintSession;->updateHintAllowed()Z+]Lcom/android/server/power/hint/HintManagerService$MyUidObserver;Lcom/android/server/power/hint/HintManagerService$MyUidObserver;]Lcom/android/server/power/hint/HintManagerService$AppHintSession;Lcom/android/server/power/hint/HintManagerService$AppHintSession;
HSPLcom/android/server/power/hint/HintManagerService$AppHintSession;->updateTargetWorkDuration(J)V
HSPLcom/android/server/power/hint/HintManagerService$BinderService;-><init>(Lcom/android/server/power/hint/HintManagerService;)V
HSPLcom/android/server/power/hint/HintManagerService$BinderService;->createHintSession(Landroid/os/IBinder;[IJ)Landroid/os/IHintSession;
@@ -9224,13 +9224,13 @@
HSPLcom/android/server/power/hint/HintManagerService$NativeWrapper;->halInit()V
HSPLcom/android/server/power/hint/HintManagerService$NativeWrapper;->halReportActualWorkDuration(J[J[J)V
HSPLcom/android/server/power/hint/HintManagerService$NativeWrapper;->halSendHint(JI)V
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver$$ExternalSyntheticLambda1;-><init>(Lcom/android/server/power/hint/HintManagerService$UidObserver;II)V
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver$$ExternalSyntheticLambda1;->run()V
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver;->$r8$lambda$ej6eMAvAGZXPb5YUxpIPNazvUW4(Lcom/android/server/power/hint/HintManagerService$UidObserver;II)V
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver;-><init>(Lcom/android/server/power/hint/HintManagerService;)V
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver;->isUidForeground(I)Z+]Ljava/lang/Integer;Ljava/lang/Integer;]Landroid/util/SparseArray;Landroid/util/SparseArray;
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver;->lambda$onUidStateChanged$1(II)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Landroid/util/SparseArray;Landroid/util/SparseArray;]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;]Landroid/util/ArraySet;Landroid/util/ArraySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
-HSPLcom/android/server/power/hint/HintManagerService$UidObserver;->onUidStateChanged(IIJI)V+]Landroid/os/Handler;Landroid/os/Handler;
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver$$ExternalSyntheticLambda1;-><init>(Lcom/android/server/power/hint/HintManagerService$MyUidObserver;II)V
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver$$ExternalSyntheticLambda1;->run()V
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver;->$r8$lambda$ej6eMAvAGZXPb5YUxpIPNazvUW4(Lcom/android/server/power/hint/HintManagerService$MyUidObserver;II)V
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver;-><init>(Lcom/android/server/power/hint/HintManagerService;)V
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver;->isUidForeground(I)Z+]Ljava/lang/Integer;Ljava/lang/Integer;]Landroid/util/SparseArray;Landroid/util/SparseArray;
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver;->lambda$onUidStateChanged$1(II)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Landroid/util/SparseArray;Landroid/util/SparseArray;]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;]Landroid/util/ArraySet;Landroid/util/ArraySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
+HSPLcom/android/server/power/hint/HintManagerService$MyUidObserver;->onUidStateChanged(IIJI)V+]Landroid/os/Handler;Landroid/os/Handler;
HSPLcom/android/server/power/hint/HintManagerService;->-$$Nest$fgetmActiveSessions(Lcom/android/server/power/hint/HintManagerService;)Landroid/util/ArrayMap;
HSPLcom/android/server/power/hint/HintManagerService;->-$$Nest$fgetmLock(Lcom/android/server/power/hint/HintManagerService;)Ljava/lang/Object;
HSPLcom/android/server/power/hint/HintManagerService;->-$$Nest$fgetmNativeWrapper(Lcom/android/server/power/hint/HintManagerService;)Lcom/android/server/power/hint/HintManagerService$NativeWrapper;
@@ -10370,8 +10370,8 @@
HPLcom/android/server/vibrator/Vibration;-><init>(Landroid/os/IBinder;Lcom/android/server/vibrator/Vibration$CallerInfo;)V
HPLcom/android/server/vibrator/VibrationEffectAdapters;->apply(Landroid/os/VibrationEffect;Ljava/util/List;Ljava/lang/Object;)Landroid/os/VibrationEffect;+]Ljava/util/List;Ljava/util/Arrays$ArrayList;]Lcom/android/server/vibrator/VibrationEffectAdapters$SegmentsAdapter;Lcom/android/server/vibrator/StepToRampAdapter;,Lcom/android/server/vibrator/RampDownAdapter;,Lcom/android/server/vibrator/RampToStepAdapter;,Lcom/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter;]Landroid/os/VibrationEffect$Composed;Landroid/os/VibrationEffect$Composed;
HPLcom/android/server/vibrator/VibrationScaler;->scale(Landroid/os/VibrationEffect;I)Landroid/os/VibrationEffect;
-HPLcom/android/server/vibrator/VibrationSettings$UidObserver;->isUidForeground(I)Z
-HSPLcom/android/server/vibrator/VibrationSettings$UidObserver;->onUidStateChanged(IIJI)V+]Landroid/util/SparseArray;Landroid/util/SparseArray;
+HPLcom/android/server/vibrator/VibrationSettings$MyUidObserver;->isUidForeground(I)Z
+HSPLcom/android/server/vibrator/VibrationSettings$MyUidObserver;->onUidStateChanged(IIJI)V+]Landroid/util/SparseArray;Landroid/util/SparseArray;
HPLcom/android/server/vibrator/VibrationSettings;->getCurrentIntensity(I)I
HPLcom/android/server/vibrator/VibrationSettings;->shouldIgnoreVibration(Lcom/android/server/vibrator/Vibration$CallerInfo;)Lcom/android/server/vibrator/Vibration$Status;
HPLcom/android/server/vibrator/VibrationStats$StatsInfo;-><init>(IIILcom/android/server/vibrator/Vibration$Status;Lcom/android/server/vibrator/VibrationStats;J)V
@@ -13493,7 +13493,7 @@
Lcom/android/server/power/hint/HintManagerService$BinderService;
Lcom/android/server/power/hint/HintManagerService$Injector;
Lcom/android/server/power/hint/HintManagerService$NativeWrapper;
-Lcom/android/server/power/hint/HintManagerService$UidObserver;
+Lcom/android/server/power/hint/HintManagerService$MyUidObserver;
Lcom/android/server/power/hint/HintManagerService;
Lcom/android/server/power/stats/BatteryExternalStatsWorker$$ExternalSyntheticLambda0;
Lcom/android/server/power/stats/BatteryExternalStatsWorker$$ExternalSyntheticLambda3;
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationEventLogger.java b/services/autofill/java/com/android/server/autofill/FieldClassificationEventLogger.java
new file mode 100644
index 0000000..ffb4632
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationEventLogger.java
@@ -0,0 +1,94 @@
+/*
+ * 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.autofill;
+
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Optional;
+
+/**
+ * Helper class to log Field Classification stats.
+ */
+public final class FieldClassificationEventLogger {
+ private static final String TAG = "FieldClassificationEventLogger";
+ private Optional<FieldClassificationEventInternal> mEventInternal;
+
+ private FieldClassificationEventLogger() {
+ mEventInternal = Optional.empty();
+ }
+
+ /**
+ * A factory constructor to create FieldClassificationEventLogger.
+ */
+ public static FieldClassificationEventLogger createLogger() {
+ return new FieldClassificationEventLogger();
+ }
+
+ /**
+ * Reset mEventInternal before logging for a new request. It shall be called for each
+ * FieldClassification request.
+ */
+ public void startNewLogForRequest() {
+ if (!mEventInternal.isEmpty()) {
+ Slog.w(TAG, "FieldClassificationEventLogger is not empty before starting for a new "
+ + "request");
+ }
+ mEventInternal = Optional.of(new FieldClassificationEventInternal());
+ }
+
+ /**
+ * Set latency as long as mEventInternal presents.
+ */
+ public void maybeSetLatencyMillis(long timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencyClassificationRequestMillis = timestamp;
+ });
+ }
+
+ /**
+ * Log an AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED event.
+ */
+ public void logAndEndEvent() {
+ if (!mEventInternal.isPresent()) {
+ Slog.w(TAG, "Shouldn't be logging AutofillFieldClassificationEventInternal again for "
+ + "same event");
+ return;
+ }
+ FieldClassificationEventInternal event = mEventInternal.get();
+ if (sVerbose) {
+ Slog.v(TAG, "Log AutofillFieldClassificationEventReported:"
+ + " mLatencyClassificationRequestMillis="
+ + event.mLatencyClassificationRequestMillis);
+ }
+ FrameworkStatsLog.write(
+ AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED,
+ event.mLatencyClassificationRequestMillis);
+ mEventInternal = Optional.empty();
+ }
+
+ private static final class FieldClassificationEventInternal {
+ long mLatencyClassificationRequestMillis = -1;
+
+ FieldClassificationEventInternal() {
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java b/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
index 6b8246c..8f2ab71 100644
--- a/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
@@ -17,16 +17,16 @@
package com.android.server.autofill;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_CANCELLED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_SESSION_DESTROYED;
@@ -37,12 +37,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.provider.Settings;
import android.service.autofill.Dataset;
-import android.text.TextUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
@@ -154,6 +149,9 @@
// succeeded.
public static final int AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT = -1;
+ // Log a magic number to indicate that the FillResponse contains a saveTriggerId.
+ public static final int HAVE_SAVE_TRIGGER_ID = 1;
+
private final int mSessionId;
private Optional<FillResponseEventInternal> mEventInternal;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
index 99a2291..feae56e 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -30,6 +30,7 @@
import android.content.pm.ServiceInfo;
import android.os.ICancellationSignal;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.service.assist.classification.FieldClassificationRequest;
import android.service.assist.classification.FieldClassificationResponse;
import android.service.assist.classification.FieldClassificationService;
@@ -132,7 +133,7 @@
public void onFieldClassificationRequest(@NonNull FieldClassificationRequest request,
FieldClassificationServiceCallbacks fieldClassificationServiceCallbacks) {
-
+ final long startTime = SystemClock.elapsedRealtime();
if (sVerbose) {
Slog.v(TAG, "onFieldClassificationRequest request:" + request);
}
@@ -144,6 +145,7 @@
new IFieldClassificationCallback.Stub() {
@Override
public void onCancellable(ICancellationSignal cancellation) {
+ logLatency(startTime);
if (sDebug) {
Log.d(TAG, "onCancellable");
}
@@ -151,15 +153,15 @@
@Override
public void onSuccess(FieldClassificationResponse response) {
+ logLatency(startTime);
if (sDebug) {
Log.d(TAG, "onSuccess Response: " + response);
}
- fieldClassificationServiceCallbacks
- .onClassificationRequestSuccess(response);
}
@Override
public void onFailure() {
+ logLatency(startTime);
if (sDebug) {
Log.d(TAG, "onFailure");
}
@@ -174,4 +176,12 @@
public void cancel() throws RemoteException {}
}));
}
+
+ private void logLatency(long startTime) {
+ final FieldClassificationEventLogger logger = FieldClassificationEventLogger.createLogger();
+ logger.startNewLogForRequest();
+ logger.maybeSetLatencyMillis(
+ SystemClock.elapsedRealtime() - startTime);
+ logger.logAndEndEvent();
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index 4b7d5bd..e5435c2 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -23,8 +23,10 @@
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NONE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_SAVE_INFO;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SESSION_DESTROYED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET;
@@ -32,11 +34,6 @@
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -74,10 +71,12 @@
NO_SAVE_REASON_NONE,
NO_SAVE_REASON_NO_SAVE_INFO,
NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG,
+ NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG,
NO_SAVE_REASON_HAS_EMPTY_REQUIRED,
NO_SAVE_REASON_NO_VALUE_CHANGED,
NO_SAVE_REASON_FIELD_VALIDATION_FAILED,
- NO_SAVE_REASON_DATASET_MATCH
+ NO_SAVE_REASON_DATASET_MATCH,
+ NO_SAVE_REASON_SESSION_DESTROYED
})
@Retention(RetentionPolicy.SOURCE)
public @interface SaveUiNotShownReason {
@@ -108,6 +107,10 @@
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_FIELD_VALIDATION_FAILED;
public static final int NO_SAVE_REASON_DATASET_MATCH =
AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_DATASET_MATCH;
+ public static final int NO_SAVE_REASON_SESSION_DESTROYED =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_SESSION_DESTROYED;
+ public static final int NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
private final int mSessionId;
private Optional<SaveEventInternal> mEventInternal;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index fe2a3ba..8d039fc 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -45,6 +45,7 @@
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT;
+import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS;
@@ -58,8 +59,22 @@
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_CHANGED;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_DATASET_MATCH;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_FIELD_VALIDATION_FAILED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_HAS_EMPTY_REQUIRED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NONE;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_SAVE_INFO;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_SESSION_DESTROYED;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
+import static com.android.server.autofill.SaveEventLogger.NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG;
+import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE;
+import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE;
+import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET;
+import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_UNKNOWN;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -457,6 +472,14 @@
@GuardedBy("mLock")
private FillResponseEventLogger mFillResponseEventLogger;
+ @NonNull
+ @GuardedBy("mLock")
+ private SaveEventLogger mSaveEventLogger;
+
+ @NonNull
+ @GuardedBy("mLock")
+ private SessionCommittedEventLogger mSessionCommittedEventLogger;
+
/**
* Fill dialog request would likely be sent slightly later.
*/
@@ -1327,6 +1350,8 @@
mPresentationStatsEventLogger = PresentationStatsEventLogger.forSessionId(sessionId);
mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId);
mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId);
+ mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId);
+ mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
synchronized (mLock) {
mSessionFlags = new SessionFlags();
mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
@@ -2048,6 +2073,12 @@
@Override
public void onSaveRequestSuccess(@NonNull String servicePackageName,
@Nullable IntentSender intentSender) {
+ // Log onSaveRequest result.
+ mSaveEventLogger.maybeSetIsSaved(true);
+ final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
+
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
@@ -2060,6 +2091,8 @@
LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
.setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
mMetricsLogger.write(log);
+
+
if (intentSender != null) {
if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
startIntentSenderAndFinishSession(intentSender);
@@ -2074,6 +2107,12 @@
public void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName) {
boolean showMessage = !TextUtils.isEmpty(message);
+
+ // Log onSaveRequest result.
+ final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
+
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
@@ -2099,6 +2138,7 @@
}
mMetricsLogger.write(log);
+
if (showMessage) {
getUiForShowing().showError(message, this);
}
@@ -2188,13 +2228,17 @@
// AutoFillUiCallback
@Override
public void save() {
+ mSaveEventLogger.maybeSetSaveButtonClicked(true);
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#save() rejected - session: "
+ id + " destroyed");
+ mSaveEventLogger.logAndEndEvent();
return;
}
}
+ final long saveRequestStartTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveRequestMillis(saveRequestStartTimestamp);
mHandler.sendMessage(obtainMessage(
AutofillManagerServiceImpl::handleSessionSave,
mService, this));
@@ -2203,12 +2247,14 @@
// AutoFillUiCallback
@Override
public void cancelSave() {
+ mSaveEventLogger.maybeSetDialogDismissed(true);
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
if (mDestroyed) {
Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
+ id + " destroyed");
+ mSaveEventLogger.logAndEndEvent();
return;
}
}
@@ -3031,6 +3077,8 @@
if (mDestroyed) {
Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
+ id + " destroyed");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_NONE);
}
@@ -3050,6 +3098,8 @@
*/
if (saveInfo == null) {
if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NO_SAVE_INFO);
}
@@ -3057,6 +3107,8 @@
if ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0) {
// TODO(b/113281366): log metrics
if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false,
Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG);
}
@@ -3130,6 +3182,8 @@
+ "didn't change: " + value);
}
changed = false;
+ } else {
+ mSaveEventLogger.maybeSetIsNewField(true);
}
} else {
isUpdate = true;
@@ -3153,6 +3207,9 @@
int saveDialogNotShowReason;
if (!allRequiredAreNotEmpty) {
saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED;
+
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_HAS_EMPTY_REQUIRED);
+ mSaveEventLogger.logAndEndEvent();
} else {
// Must look up all optional ids in 2 scenarios:
// - if no required id changed but an optional id did, it should trigger save / update
@@ -3188,6 +3245,8 @@
}
if (filledValue != null) {
isUpdate = true;
+ } else {
+ mSaveEventLogger.maybeSetIsNewField(true);
}
atLeastOneChanged = true;
}
@@ -3206,6 +3265,8 @@
}
if (!atLeastOneChanged) {
saveDialogNotShowReason = Event.NO_SAVE_UI_REASON_NO_VALUE_CHANGED;
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_VALUE_CHANGED);
+ mSaveEventLogger.logAndEndEvent();
} else {
if (sDebug) {
Slog.d(TAG, "at least one field changed, validate fields for save UI");
@@ -3224,6 +3285,9 @@
Slog.e(TAG, "Not showing save UI because validation failed:", e);
log.setType(MetricsEvent.TYPE_FAILURE);
mMetricsLogger.write(log);
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(
+ NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
}
@@ -3231,6 +3295,9 @@
mMetricsLogger.write(log);
if (!isValid) {
Slog.i(TAG, "not showing save UI because fields failed validation");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(
+ NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED);
}
@@ -3271,6 +3338,8 @@
Slog.d(TAG, "ignoring Save UI because all fields match contents of "
+ "dataset #" + i + ": " + dataset);
}
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_DATASET_MATCH);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_DATASET_MATCH);
}
@@ -3292,14 +3361,18 @@
}
if (serviceLabel == null || serviceIcon == null) {
wtf(null, "showSaveLocked(): no service label or icon");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
+ mSaveEventLogger.logAndEndEvent();
return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true,
Event.NO_SAVE_UI_REASON_NONE);
}
-
+ final long saveUiDisplayStartTimestamp = SystemClock.elapsedRealtime();
getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
mService.getServicePackageName(), saveInfo, this,
mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode,
response.getShowSaveDialogIcon());
+ mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis(
+ SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp);
if (client != null) {
try {
client.setSaveUiState(id, true);
@@ -3471,11 +3544,15 @@
if (mDestroyed) {
Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
+ id + " destroyed");
+ mSaveEventLogger.maybeSetIsSaved(false);
+ mSaveEventLogger.logAndEndEvent();
return;
}
if (mRemoteFillService == null) {
wtf(null, "callSaveLocked() called without a remote service. "
+ "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+ mSaveEventLogger.maybeSetIsSaved(false);
+ mSaveEventLogger.logAndEndEvent();
return;
}
@@ -3483,6 +3560,8 @@
if (mContexts == null) {
Slog.w(TAG, "callSaveLocked(): no contexts");
+ mSaveEventLogger.maybeSetIsSaved(false);
+ mSaveEventLogger.logAndEndEvent();
return;
}
@@ -3899,6 +3978,8 @@
mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
+ mFillResponseEventLogger.maybeSetAvailableCount(
+ response.getDatasets(), mCurrentViewId);
}
if (isSameViewEntered) {
@@ -4069,6 +4150,11 @@
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillReady() rejected - session: "
+ id + " destroyed");
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED);
+ mSaveEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+ NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
+ mPresentationStatsEventLogger.logAndEndEvent();
return;
}
}
@@ -4100,7 +4186,7 @@
synchronized (mLock) {
// Time passed since Session was created
- long suggestionSentRelativeTimestamp =
+ final long suggestionSentRelativeTimestamp =
SystemClock.elapsedRealtime() - mLatencyBaseTime;
mPresentationStatsEventLogger.maybeSetSuggestionSentTimestampMs(
(int) (suggestionSentRelativeTimestamp));
@@ -4468,10 +4554,19 @@
saveTriggerId = saveInfo.getTriggerId();
if (saveTriggerId != null) {
writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
+ mSaveEventLogger.maybeSetSaveUiShownReason(SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET);
}
flags = saveInfo.getFlags();
mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
+ mFillResponseEventLogger.maybeSetSaveUiTriggerIds(HAVE_SAVE_TRIGGER_ID);
+
+ // Start to log Save event.
+ mSaveEventLogger.maybeSetRequestId(response.getRequestId());
+ mSaveEventLogger.maybeSetAppPackageUid(uid);
+ mSaveEventLogger.maybeSetSaveUiTriggerIds(HAVE_SAVE_TRIGGER_ID);
+ mSaveEventLogger.maybeSetFlag(flags);
+
// We only need to track views if we want to save once they become invisible.
if (mSaveOnAllViewsInvisible) {
if (trackedViews == null) {
@@ -4479,18 +4574,28 @@
}
if (saveInfo.getRequiredIds() != null) {
Collections.addAll(trackedViews, saveInfo.getRequiredIds());
+ mSaveEventLogger.maybeSetSaveUiShownReason(
+ SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE);
}
if (saveInfo.getOptionalIds() != null) {
Collections.addAll(trackedViews, saveInfo.getOptionalIds());
+ mSaveEventLogger.maybeSetSaveUiShownReason(
+ SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE);
}
}
if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
+ mSaveEventLogger.maybeSetSaveUiShownReason(
+ SAVE_UI_SHOWN_REASON_UNKNOWN);
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(
+ NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG);
saveOnFinish = false;
}
} else {
flags = 0;
+ mSaveEventLogger.maybeSetSaveUiNotShownReason(
+ NO_SAVE_REASON_NO_SAVE_INFO);
saveTriggerId = null;
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 092eb4e..d54aa7c 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -301,25 +301,31 @@
int sdk = Build.VERSION.SDK_INT;
String release = Build.VERSION.RELEASE;
- if (Build.isDebuggable()) {
- // Debug builds cannot pass attestation verification. Use hardcoded key instead.
+
+ if (sdk < SECURE_CHANNEL_AVAILABLE_SDK || remoteSdk < SECURE_CHANNEL_AVAILABLE_SDK) {
+ // If either device is Android T or below, use raw channel
+ // TODO: depending on the release version, either
+ // 1) using a RawTransport for old T versions
+ // 2) or an Ukey2 handshaked transport for UKey2 backported T versions
+ Slog.d(TAG, "Secure channel is not supported. Using raw transport");
+ transport = new RawTransport(transport.getAssociationId(), transport.getFd(), mContext);
+ } else if (Build.isDebuggable()) {
+ // If device is debug build, use hardcoded test key for authentication
Slog.d(TAG, "Creating an unauthenticated secure channel");
final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
mContext, testKey, null);
- } else if (remoteSdk == NON_ANDROID) {
+ } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) {
+ // If either device is not Android, then use app-specific pre-shared key
// TODO: pass in a real preSharedKey
+ Slog.d(TAG, "Creating a PSK-authenticated secure channel");
transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
mContext, new byte[0], null);
- } else if (sdk >= SECURE_CHANNEL_AVAILABLE_SDK
- && remoteSdk >= SECURE_CHANNEL_AVAILABLE_SDK) {
- Slog.i(TAG, "Creating a secure channel");
+ } else {
+ // If none of the above applies, then use secure channel with attestation verification
+ Slog.d(TAG, "Creating a secure channel");
transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
mContext);
- } else {
- // TODO: depending on the release version, either
- // 1) using a RawTransport for old T versions
- // 2) or an Ukey2 handshaked transport for UKey2 backported T versions
}
addMessageListenersToTransport(transport);
transport.start();
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 1363ef3..ae88f24 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -126,7 +126,7 @@
private final VirtualDeviceManagerService mService;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
- private final int mDeviceId;
+ private int mDeviceId;
// Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
// Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
// 1. After display is created the window manager calls into VDM during construction
@@ -348,6 +348,7 @@
@Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
+ Objects.requireNonNull(pendingIntent);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException("Display ID " + displayId
@@ -404,6 +405,7 @@
super.close_enforcePermission();
// Remove about-to-be-closed virtual device from the service before butchering it.
mService.removeVirtualDevice(mDeviceId);
+ mDeviceId = Context.DEVICE_ID_INVALID;
VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
synchronized (mVirtualDeviceLock) {
@@ -497,6 +499,7 @@
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
+ Objects.requireNonNull(config);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
@@ -517,6 +520,7 @@
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
super.createVirtualKeyboard_enforcePermission();
+ Objects.requireNonNull(config);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
@@ -539,6 +543,7 @@
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
super.createVirtualMouse_enforcePermission();
+ Objects.requireNonNull(config);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
@@ -560,6 +565,7 @@
public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
@NonNull IBinder deviceToken) {
super.createVirtualTouchscreen_enforcePermission();
+ Objects.requireNonNull(config);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
@@ -590,6 +596,7 @@
public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
@NonNull IBinder deviceToken) {
super.createVirtualNavigationTouchpad_enforcePermission();
+ Objects.requireNonNull(config);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 3b1983f..291c0587 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -66,7 +66,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -86,6 +89,14 @@
private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
Context.DEVICE_ID_DEFAULT + 1);
+ private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
+ new CompanionDeviceManager.OnAssociationsChangedListener() {
+ @Override
+ public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
+ syncVirtualDevicesToCdmAssociations(associations);
+ }
+ };
+
/**
* Mapping from device IDs to virtual devices.
*/
@@ -204,11 +215,56 @@
final long identity = Binder.clearCallingIdentity();
try {
getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+
+ synchronized (mVirtualDeviceManagerLock) {
+ if (mVirtualDevices.size() == 0) {
+ unregisterCdmAssociationListener();
+ }
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
+ Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
+ synchronized (mVirtualDeviceManagerLock) {
+ if (mVirtualDevices.size() == 0) {
+ return;
+ }
+
+ Set<Integer> activeAssociationIds = new HashSet<>(associations.size());
+ for (AssociationInfo association : associations) {
+ activeAssociationIds.add(association.getId());
+ }
+
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
+ if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+ virtualDevicesToRemove.add(virtualDevice);
+ }
+ }
+ }
+
+ for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
+ virtualDevice.close();
+ }
+
+ }
+
+ private void registerCdmAssociationListener() {
+ final CompanionDeviceManager cdm = getContext().getSystemService(
+ CompanionDeviceManager.class);
+ cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
+ mCdmAssociationListener);
+ }
+
+ private void unregisterCdmAssociationListener() {
+ final CompanionDeviceManager cdm = getContext().getSystemService(
+ CompanionDeviceManager.class);
+ cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
@@ -254,7 +310,20 @@
if (associationInfo == null) {
throw new IllegalArgumentException("No association with ID " + associationId);
}
+ Objects.requireNonNull(params);
+ Objects.requireNonNull(activityListener);
+ Objects.requireNonNull(soundEffectListener);
+
synchronized (mVirtualDeviceManagerLock) {
+ if (mVirtualDevices.size() == 0) {
+ final long callindId = Binder.clearCallingIdentity();
+ try {
+ registerCdmAssociationListener();
+ } finally {
+ Binder.restoreCallingIdentity(callindId);
+ }
+ }
+
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
getCameraAccessController(userHandle);
@@ -275,6 +344,7 @@
public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName)
throws RemoteException {
+ Objects.requireNonNull(virtualDisplayConfig);
final int callingUid = getCallingUid();
if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
throw new SecurityException(
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index c5eb25b..3fd6fe8 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -26,8 +26,8 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
-import android.app.IUidObserver;
import android.app.SearchManager;
+import android.app.UidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -360,35 +360,18 @@
private void registerUidListener() {
try {
- mAm.registerUidObserver(new IUidObserver.Stub() {
+ mAm.registerUidObserver(new UidObserver() {
@Override
- public void onUidGone(int uid, boolean disabled) throws RemoteException {
+ public void onUidGone(int uid, boolean disabled) {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
PinnerService::handleUidGone, PinnerService.this, uid));
}
@Override
- public void onUidActive(int uid) throws RemoteException {
+ public void onUidActive(int uid) {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
PinnerService::handleUidActive, PinnerService.this, uid));
}
-
- @Override
- public void onUidIdle(int uid, boolean disabled) throws RemoteException {
- }
-
- @Override
- public void onUidStateChanged(int uid, int procState, long procStateSeq,
- int capability) throws RemoteException {
- }
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
- }
-
- @Override
- public void onUidProcAdjChanged(int uid) throws RemoteException {
- }
}, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register uid observer", e);
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 553706d..ae5dbe1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -151,6 +151,7 @@
static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
+ static final String KEY_USE_MODERN_TRIM = "use_modern_trim";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
@@ -212,6 +213,8 @@
private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
+ private static final boolean DEFAULT_USE_MODERN_TRIM = false;
+
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
*/
@@ -1052,6 +1055,9 @@
/** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
+ /** @see #KEY_USE_MODERN_TRIM */
+ public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM;
+
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -1227,6 +1233,9 @@
case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
updateUseTieredCachedAdj();
break;
+ case KEY_USE_MODERN_TRIM:
+ updateUseModernTrim();
+ break;
default:
updateFGSPermissionEnforcementFlagsIfNecessary(name);
break;
@@ -1302,6 +1311,13 @@
com.android.internal.R.integer.config_customizedMaxCachedProcesses);
CUR_MAX_CACHED_PROCESSES = mCustomizedMaxCachedProcesses;
CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
+
+ final int rawMaxEmptyProcesses = computeEmptyProcessLimit(
+ Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES));
+ CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2;
+ CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
+ - rawMaxEmptyProcesses) / 3;
+
}
public void start(ContentResolver resolver) {
@@ -1867,12 +1883,11 @@
}
CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
- // Note the trim levels do NOT depend on the override process limit, we want
- // to consider the same level the point where we do trimming regardless of any
- // additional enforced limit.
- final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
- CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
- CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
+ final int rawMaxEmptyProcesses = computeEmptyProcessLimit(
+ Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES));
+ CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2;
+ CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
+ - rawMaxEmptyProcesses) / 3;
}
private void updateProactiveKillsEnabled() {
@@ -1991,6 +2006,13 @@
DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
}
+ private void updateUseModernTrim() {
+ USE_MODERN_TRIM = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_USE_MODERN_TRIM,
+ DEFAULT_USE_MODERN_TRIM);
+ }
+
private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) {
ForegroundServiceTypePolicy.getDefaultPolicy()
.updatePermissionEnforcementFlagIfNecessary(name);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 350ac3b..e080a80 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -59,12 +59,12 @@
import android.app.IActivityTaskManager;
import android.app.IProcessObserver;
import android.app.IStopUserCallback;
-import android.app.IUidObserver;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.ProcessStateEnum;
import android.app.ProfilerInfo;
import android.app.RemoteServiceException.CrashedByAdbException;
+import android.app.UidObserver;
import android.app.UserSwitchObserver;
import android.app.WaitResult;
import android.app.usage.AppStandbyInfo;
@@ -1858,7 +1858,7 @@
return 0;
}
- static final class MyUidObserver extends IUidObserver.Stub
+ static final class MyUidObserver extends UidObserver
implements ActivityManagerService.OomAdjObserver {
final IActivityManager mInterface;
final ActivityManagerService mInternal;
@@ -1883,8 +1883,7 @@
}
@Override
- public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability)
- throws RemoteException {
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -1903,7 +1902,7 @@
}
@Override
- public void onUidGone(int uid, boolean disabled) throws RemoteException {
+ public void onUidGone(int uid, boolean disabled) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -1921,7 +1920,7 @@
}
@Override
- public void onUidActive(int uid) throws RemoteException {
+ public void onUidActive(int uid) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -1935,7 +1934,7 @@
}
@Override
- public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+ public void onUidIdle(int uid, boolean disabled) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -1953,7 +1952,7 @@
}
@Override
- public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
+ public void onUidCachedChanged(int uid, boolean cached) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -1967,10 +1966,6 @@
}
@Override
- public void onUidProcAdjChanged(int uid) throws RemoteException {
- }
-
- @Override
public void onOomAdjMessage(String msg) {
synchronized (this) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 08c1de6..c343ec2 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -534,6 +534,8 @@
}
}
+ mService.mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(initialPid,
+ CachedAppOptimizer.UNFREEZE_REASON_PROCESS_END);
proc.scheduleCrashLocked(message, exceptionTypeId, extras);
if (force) {
// If the app is responsive, the scheduled crash will happen as expected
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index ccd7390..25ac956 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1005,6 +1005,37 @@
mBgHandler.obtainMessage(BgHandler.MEMORY_PRESSURE_CHANGED, mLastMemoryLevel, memFactor)
.sendToTarget();
}
+
+ if (mService.mConstants.USE_MODERN_TRIM) {
+ // Modern trim is not sent based on lowmem state
+ // Dispatch UI_HIDDEN to processes that need it
+ mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
+ final ProcessProfileRecord profile = app.mProfile;
+ final IApplicationThread thread;
+ final ProcessStateRecord state = app.mState;
+ if (state.hasProcStateChanged()) {
+ state.setProcStateChanged(false);
+ }
+ int procState = app.mState.getCurProcState();
+ if (((procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ && procState < ActivityManager.PROCESS_STATE_CACHED_ACTIVITY)
+ || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
+ // If this application is now in the background and it
+ // had done UI, then give it the special trim level to
+ // have it free UI resources.
+ if ((thread = app.getThread()) != null) {
+ try {
+ thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ app.mProfile.setPendingUiClean(false);
+ } catch (RemoteException e) {
+
+ }
+ }
+ }
+ });
+ return false;
+ }
+
mLastMemoryLevel = memFactor;
mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
boolean allChanged;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d5b8bb4..dbb351b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1050,13 +1050,13 @@
* Check overall health, confirming things are in a reasonable state and
* that we're not wedged.
*/
- public void checkHealthLocked() {
- checkHealthLocked(mPending);
- checkHealthLocked(mPendingUrgent);
- checkHealthLocked(mPendingOffload);
+ public void assertHealthLocked() {
+ assertHealthLocked(mPending);
+ assertHealthLocked(mPendingUrgent);
+ assertHealthLocked(mPendingOffload);
}
- private void checkHealthLocked(@NonNull ArrayDeque<SomeArgs> queue) {
+ private void assertHealthLocked(@NonNull ArrayDeque<SomeArgs> queue) {
if (queue.isEmpty()) return;
final Iterator<SomeArgs> it = queue.descendingIterator();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b18997a..a4bdf61 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -246,21 +246,15 @@
private final Handler.Callback mLocalCallback = (msg) -> {
switch (msg.what) {
case MSG_UPDATE_RUNNING_LIST: {
- synchronized (mService) {
- updateRunningListLocked();
- }
+ updateRunningList();
return true;
}
case MSG_DELIVERY_TIMEOUT_SOFT: {
- synchronized (mService) {
- deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
- }
+ deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1);
return true;
}
case MSG_DELIVERY_TIMEOUT_HARD: {
- synchronized (mService) {
- deliveryTimeoutHardLocked((BroadcastProcessQueue) msg.obj);
- }
+ deliveryTimeoutHard((BroadcastProcessQueue) msg.obj);
return true;
}
case MSG_BG_ACTIVITY_START_TIMEOUT: {
@@ -274,9 +268,7 @@
return true;
}
case MSG_CHECK_HEALTH: {
- synchronized (mService) {
- checkHealthLocked();
- }
+ checkHealth();
return true;
}
}
@@ -365,6 +357,12 @@
}
}
+ private void updateRunningList() {
+ synchronized (mService) {
+ updateRunningListLocked();
+ }
+ }
+
/**
* Consider updating the list of "running" queues.
* <p>
@@ -965,6 +963,13 @@
r.resultTo = null;
}
+ private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue,
+ int softTimeoutMillis) {
+ synchronized (mService) {
+ deliveryTimeoutSoftLocked(queue, softTimeoutMillis);
+ }
+ }
+
private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
int softTimeoutMillis) {
if (queue.app != null) {
@@ -981,6 +986,12 @@
}
}
+ private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) {
+ synchronized (mService) {
+ deliveryTimeoutHardLocked(queue);
+ }
+ }
+
private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
"deliveryTimeoutHardLocked");
@@ -1458,52 +1469,19 @@
// TODO: implement
}
- /**
- * Check overall health, confirming things are in a reasonable state and
- * that we're not wedged. If we determine we're in an unhealthy state, dump
- * current state once and stop future health checks to avoid spamming.
- */
- @VisibleForTesting
- void checkHealthLocked() {
+ private void checkHealth() {
+ synchronized (mService) {
+ checkHealthLocked();
+ }
+ }
+
+ private void checkHealthLocked() {
try {
- // Verify all runnable queues are sorted
- BroadcastProcessQueue prev = null;
- BroadcastProcessQueue next = mRunnableHead;
- while (next != null) {
- checkState(next.runnableAtPrev == prev, "runnableAtPrev");
- checkState(next.isRunnable(), "isRunnable " + next);
- if (prev != null) {
- checkState(next.getRunnableAt() >= prev.getRunnableAt(),
- "getRunnableAt " + next + " vs " + prev);
- }
- prev = next;
- next = next.runnableAtNext;
- }
-
- // Verify all running queues are active
- for (BroadcastProcessQueue queue : mRunning) {
- if (queue != null) {
- checkState(queue.isActive(), "isActive " + queue);
- }
- }
-
- // Verify that pending cold start hasn't been orphaned
- if (mRunningColdStart != null) {
- checkState(getRunningIndexOf(mRunningColdStart) >= 0,
- "isOrphaned " + mRunningColdStart);
- }
-
- // Verify health of all known process queues
- for (int i = 0; i < mProcessQueues.size(); i++) {
- BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
- while (leaf != null) {
- leaf.checkHealthLocked();
- leaf = leaf.processNameNext;
- }
- }
+ assertHealthLocked();
// If no health issues found above, check again in the future
- mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH, DateUtils.MINUTE_IN_MILLIS);
+ mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH,
+ DateUtils.MINUTE_IN_MILLIS);
} catch (Exception e) {
// Throw up a message to indicate that something went wrong, and
@@ -1513,6 +1491,50 @@
}
}
+ /**
+ * Check overall health, confirming things are in a reasonable state and
+ * that we're not wedged. If we determine we're in an unhealthy state, dump
+ * current state once and stop future health checks to avoid spamming.
+ */
+ @VisibleForTesting
+ void assertHealthLocked() {
+ // Verify all runnable queues are sorted
+ BroadcastProcessQueue prev = null;
+ BroadcastProcessQueue next = mRunnableHead;
+ while (next != null) {
+ checkState(next.runnableAtPrev == prev, "runnableAtPrev");
+ checkState(next.isRunnable(), "isRunnable " + next);
+ if (prev != null) {
+ checkState(next.getRunnableAt() >= prev.getRunnableAt(),
+ "getRunnableAt " + next + " vs " + prev);
+ }
+ prev = next;
+ next = next.runnableAtNext;
+ }
+
+ // Verify all running queues are active
+ for (BroadcastProcessQueue queue : mRunning) {
+ if (queue != null) {
+ checkState(queue.isActive(), "isActive " + queue);
+ }
+ }
+
+ // Verify that pending cold start hasn't been orphaned
+ if (mRunningColdStart != null) {
+ checkState(getRunningIndexOf(mRunningColdStart) >= 0,
+ "isOrphaned " + mRunningColdStart);
+ }
+
+ // Verify health of all known process queues
+ for (int i = 0; i < mProcessQueues.size(); i++) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ leaf.assertHealthLocked();
+ leaf = leaf.processNameNext;
+ }
+ }
+ }
+
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 9c15463..78edbba 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
@@ -27,12 +28,14 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
+import android.app.IApplicationThread;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -1231,6 +1234,17 @@
return;
}
+ if (mAm.mConstants.USE_MODERN_TRIM
+ && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
+ final IApplicationThread thread = app.getThread();
+ if (thread != null) {
+ try {
+ thread.scheduleTrimMemory(TRIM_MEMORY_BACKGROUND);
+ } catch (RemoteException e) {
+ // do nothing
+ }
+ }
+ }
mFreezeHandler.sendMessageDelayed(
mFreezeHandler.obtainMessage(
SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e651e23..afae623 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,10 +16,13 @@
package com.android.server.am;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -63,7 +66,6 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
/**
* Full information about a particular process that
@@ -1350,16 +1352,19 @@
* {@param originatingToken} if you have one such originating token, this is useful for tracing
* back the grant in the case of the notification token.
*/
- void addOrUpdateBackgroundStartPrivileges(Binder entity,
- BackgroundStartPrivileges backgroundStartPrivileges) {
- Objects.requireNonNull(entity);
+ void addOrUpdateBackgroundStartPrivileges(@NonNull Binder entity,
+ @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
+ requireNonNull(entity, "entity");
+ requireNonNull(backgroundStartPrivileges, "backgroundStartPrivileges");
+ checkArgument(backgroundStartPrivileges.allowsAny(),
+ "backgroundStartPrivileges does not allow anything");
mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity,
backgroundStartPrivileges);
setBackgroundStartPrivileges(entity, backgroundStartPrivileges);
}
- void removeBackgroundStartPrivileges(Binder entity) {
- Objects.requireNonNull(entity);
+ void removeBackgroundStartPrivileges(@NonNull Binder entity) {
+ requireNonNull(entity, "entity");
mWindowProcessController.removeBackgroundStartPrivileges(entity);
setBackgroundStartPrivileges(entity, null);
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 18ef66f..edf0dbd 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -850,10 +850,11 @@
mAppForAllowingBgActivityStartsByStart =
mBackgroundStartPrivilegesByStartMerged.allowsAny()
? proc : null;
- if (mBackgroundStartPrivilegesByStartMerged.allowsAny()
- || mIsAllowedBgActivityStartsByBinding) {
+ BackgroundStartPrivileges backgroundStartPrivileges =
+ getBackgroundStartPrivilegesWithExclusiveToken();
+ if (backgroundStartPrivileges.allowsAny()) {
proc.addOrUpdateBackgroundStartPrivileges(this,
- getBackgroundStartPrivilegesWithExclusiveToken());
+ backgroundStartPrivileges);
} else {
proc.removeBackgroundStartPrivileges(this);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fc581ee..ac55f28 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -51,6 +51,7 @@
import android.app.BroadcastOptions;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.UidObserver;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
@@ -959,29 +960,16 @@
}
}
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
- @Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
- int capability) {
- }
-
+ final private IUidObserver mUidObserver = new UidObserver() {
@Override public void onUidGone(int uid, boolean disabled) {
// Once the uid is no longer running, no need to keep trying to disable its audio.
disableAudioForUid(false, uid);
}
- @Override public void onUidActive(int uid) throws RemoteException {
- }
-
- @Override public void onUidIdle(int uid, boolean disabled) {
- }
-
@Override public void onUidCachedChanged(int uid, boolean cached) {
disableAudioForUid(cached, uid);
}
- @Override public void onUidProcAdjChanged(int uid) {
- }
-
private void disableAudioForUid(boolean disable, int uid) {
queueMsgUnderWakeLock(mAudioHandler, MSG_DISABLE_AUDIO_FOR_UID,
disable ? 1 : 0 /* arg1 */, uid /* arg2 */,
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 79ce6b4..3dcea19 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.common.OperationContext;
import android.view.Surface;
@@ -65,6 +66,7 @@
* Current fold state from
* {@link android.hardware.biometrics.IBiometricContextListener.FoldState}.
*/
+ @IBiometricContextListener.FoldState
int getFoldState();
/** Current device display rotation. */
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index dea8030..21ade1b 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -84,8 +84,6 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
private final WindowManager mWindowManager;
@Nullable private final Handler mHandler;
- private boolean mIsAod = false;
- private boolean mIsAwake = false;
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
@@ -117,16 +115,6 @@
try {
service.setBiometicContextListener(new IBiometricContextListener.Stub() {
@Override
- public void onDozeChanged(boolean isAod, boolean isAwake) {
- final boolean changed = (mIsAod != isAod) || (mIsAwake != isAwake);
- if (changed) {
- mIsAod = isAod;
- mIsAwake = isAwake;
- notifyChanged();
- }
- }
-
- @Override
public void onFoldChanged(int foldState) {
mFoldState = foldState;
// no need to notify, not sent to HAL
@@ -185,12 +173,18 @@
@Override
public boolean isAod() {
- return mIsAod;
+ return mDisplayState == AuthenticateOptions.DISPLAY_STATE_AOD;
}
@Override
public boolean isAwake() {
- return mIsAwake;
+ switch (mDisplayState) {
+ case AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN:
+ case AuthenticateOptions.DISPLAY_STATE_SCREENSAVER:
+ case AuthenticateOptions.DISPLAY_STATE_UNKNOWN:
+ return true;
+ }
+ return false;
}
@Override
@@ -252,7 +246,7 @@
public String toString() {
return "[keyguard session: " + getKeyguardEntrySessionInfo() + ", "
+ "bp session: " + getBiometricPromptSessionInfo() + ", "
- + "isAod: " + isAod() + ", "
+ + "displayState: " + getDisplayState() + ", "
+ "isAwake: " + isAwake() + ", "
+ "isDisplayOn: " + isDisplayOn() + ", "
+ "dock: " + getDockedState() + ", "
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index 2934339..d1de80b 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -19,8 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
+import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.DisplayState;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
import android.hardware.biometrics.common.WakeReason;
@@ -204,11 +206,17 @@
return mIsDisplayOn;
}
- /** {@link OperationContext#isAod}. */
+ /** @deprecated prefer {@link #getDisplayState()} to {@link OperationContext#isAod}. */
public boolean isAod() {
return mAidlContext.isAod;
}
+ /** {@link OperationContext#displayState}. */
+ @DisplayState
+ public int getDisplayState() {
+ return mAidlContext.displayState;
+ }
+
/** {@link OperationContext#isCrypto}. */
public boolean isCrypto() {
return mAidlContext.isCrypto;
@@ -233,6 +241,7 @@
/** Update this object with the latest values from the given context. */
OperationContextExt update(@NonNull BiometricContext biometricContext) {
mAidlContext.isAod = biometricContext.isAod();
+ mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
setFirstSessionId(biometricContext);
mIsDisplayOn = biometricContext.isDisplayOn();
@@ -243,6 +252,21 @@
return this;
}
+ @DisplayState
+ private static int toAidlDisplayState(@AuthenticateOptions.DisplayState int state) {
+ switch (state) {
+ case AuthenticateOptions.DISPLAY_STATE_AOD:
+ return DisplayState.AOD;
+ case AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN:
+ return DisplayState.LOCKSCREEN;
+ case AuthenticateOptions.DISPLAY_STATE_NO_UI:
+ return DisplayState.NO_UI;
+ case AuthenticateOptions.DISPLAY_STATE_SCREENSAVER:
+ return DisplayState.SCREENSAVER;
+ }
+ return DisplayState.UNKNOWN;
+ }
+
private void setFirstSessionId(@NonNull BiometricContext biometricContext) {
mSessionInfo = biometricContext.getKeyguardEntrySessionInfo();
if (mSessionInfo != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 84e2fb4..7ae31b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -40,6 +40,7 @@
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
@@ -165,8 +166,17 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- return session.getSession().authenticateWithContext(
- mOperationId, getOperationContext().toAidlContext(getOptions()));
+ final OperationContextExt opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().authenticateWithContext(
+ mOperationId, opContext.toAidlContext(getOptions()));
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().authenticate(mOperationId);
}
@@ -174,6 +184,8 @@
@Override
protected void stopHalOperation() {
+ unsubscribeBiometricContext();
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index fa23ccd..9dc1782 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -31,6 +31,7 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -86,6 +87,8 @@
@Override
protected void stopHalOperation() {
+ unsubscribeBiometricContext();
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
@@ -119,8 +122,17 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- return session.getSession().detectInteractionWithContext(
- getOperationContext().toAidlContext(mOptions));
+ final OperationContextExt opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
+ opContext.toAidlContext(mOptions));
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 792b52e..722c9af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -39,6 +39,7 @@
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
@@ -198,9 +199,18 @@
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
if (session.hasContextMethods()) {
- return session.getSession().enrollWithContext(
+ final OperationContextExt opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().enrollWithContext(
hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle,
- getOperationContext().toAidlContext());
+ opContext.toAidlContext());
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
mHwPreviewHandle);
@@ -209,6 +219,8 @@
@Override
protected void stopHalOperation() {
+ unsubscribeBiometricContext();
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 435e81d..2bfc239 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -262,6 +262,14 @@
final AidlSession session = getFreshDaemon();
final OperationContextExt opContext = getOperationContext();
+ final ICancellationSignal cancel;
+ if (session.hasContextMethods()) {
+ cancel = session.getSession().authenticateWithContext(
+ mOperationId, opContext.toAidlContext(getOptions()));
+ } else {
+ cancel = session.getSession().authenticate(mOperationId);
+ }
+
getBiometricContext().subscribe(opContext, ctx -> {
if (session.hasContextMethods()) {
try {
@@ -283,12 +291,7 @@
mALSProbeCallback.getProbe().enable();
}
- if (session.hasContextMethods()) {
- return session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext(getOptions()));
- } else {
- return session.getSession().authenticate(mOperationId);
- }
+ return cancel;
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 16d16fc..46f62d3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -31,6 +31,7 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -79,12 +80,15 @@
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
+ unsubscribeBiometricContext();
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- mCallback.onClientFinished(this, false /* success */);
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
}
}
@@ -106,8 +110,17 @@
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- return session.getSession().detectInteractionWithContext(
- getOperationContext().toAidlContext(mOptions));
+ final OperationContextExt opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
+ opContext.toAidlContext(mOptions));
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index af5609a..299f865 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -75,10 +75,11 @@
private static final int MSG_UPDATE_AMBIENT_LUX = 1;
private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
- private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3;
+ private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3;
private static final int MSG_UPDATE_FOREGROUND_APP = 4;
private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
private static final int MSG_RUN_UPDATE = 6;
+ private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7;
// Callbacks for requesting updates to the display's power state
private final Callbacks mCallbacks;
@@ -216,12 +217,11 @@
private float mBrightnessAdjustmentSampleOldLux;
private float mBrightnessAdjustmentSampleOldBrightness;
- // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
- // user's adjustment) immediately, but wait for a drastic enough change in the ambient light.
- // The anchor determines what were the light levels when the user has set their preference, and
- // we use a relative threshold to determine when to revert to the OEM curve.
- private boolean mShortTermModelValid;
- private float mShortTermModelAnchor;
+ // The short term models, current and previous. Eg, we might use the "paused" one to save out
+ // the interactive short term model when switching to idle screen brightness mode, and
+ // vice-versa.
+ private final ShortTermModel mShortTermModel;
+ private final ShortTermModel mPausedShortTermModel;
// Controls High Brightness Mode.
private HighBrightnessModeController mHbmController;
@@ -309,8 +309,8 @@
mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
mScreenBrightnessThresholds = screenBrightnessThresholds;
mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
- mShortTermModelValid = true;
- mShortTermModelAnchor = -1;
+ mShortTermModel = new ShortTermModel();
+ mPausedShortTermModel = new ShortTermModel();
mHandler = new AutomaticBrightnessHandler(looper);
mAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock);
@@ -492,10 +492,10 @@
Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
}
if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) {
- mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
+ mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL,
mCurrentBrightnessMapper.getShortTermModelTimeout());
} else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
- mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
+ mHandler.removeMessages(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL);
}
return true;
}
@@ -516,25 +516,13 @@
private boolean setScreenBrightnessByUser(float lux, float brightness) {
mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
- mShortTermModelValid = true;
- mShortTermModelAnchor = lux;
- if (mLoggingEnabled) {
- Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
- }
+ mShortTermModel.setUserBrightness(lux, brightness);
return true;
}
public void resetShortTermModel() {
mCurrentBrightnessMapper.clearUserDataPoints();
- mShortTermModelValid = true;
- mShortTermModelAnchor = -1;
- }
-
- private void invalidateShortTermModel() {
- if (mLoggingEnabled) {
- Slog.d(TAG, "ShortTermModel: invalidate user data");
- }
- mShortTermModelValid = false;
+ mShortTermModel.reset();
}
public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -595,8 +583,12 @@
pw.println(" mShortTermModelTimeout(idle)="
+ mIdleModeBrightnessMapper.getShortTermModelTimeout());
}
- pw.println(" mShortTermModelAnchor=" + mShortTermModelAnchor);
- pw.println(" mShortTermModelValid=" + mShortTermModelValid);
+ pw.println(" mShortTermModel=");
+ mShortTermModel.dump(pw);
+ pw.println(" mPausedShortTermModel=");
+ mPausedShortTermModel.dump(pw);
+
+ pw.println();
pw.println(" mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
pw.println(" mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux);
pw.println(" mBrightnessAdjustmentSampleOldBrightness="
@@ -740,15 +732,9 @@
}
mHbmController.onAmbientLuxChange(mAmbientLux);
+
// If the short term model was invalidated and the change is drastic enough, reset it.
- if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
- if (mCurrentBrightnessMapper.shouldResetShortTermModel(
- mAmbientLux, mShortTermModelAnchor)) {
- resetShortTermModel();
- } else {
- mShortTermModelValid = true;
- }
- }
+ mShortTermModel.maybeReset(mAmbientLux);
}
private float calculateAmbientLux(long now, long horizon) {
@@ -1118,8 +1104,29 @@
return;
}
Slog.i(TAG, "Switching to Idle Screen Brightness Mode");
+ // Stash short term model
+ ShortTermModel tempShortTermModel = new ShortTermModel();
+ tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
+ mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);
+
+ // Send delayed timeout
+ mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
+ mClock.uptimeMillis()
+ + mCurrentBrightnessMapper.getShortTermModelTimeout());
+
+ Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel);
+ // new brightness mapper
mCurrentBrightnessMapper = mIdleModeBrightnessMapper;
- resetShortTermModel();
+
+ // if previous stm has been invalidated, and lux has drastically changed, just use
+ // the new, reset stm.
+ // if previous stm is still valid then revalidate it
+ if (mPausedShortTermModel != null && !mPausedShortTermModel.maybeReset(mAmbientLux)) {
+ setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
+ mPausedShortTermModel.mBrightness);
+ }
+ mPausedShortTermModel.copyFrom(tempShortTermModel);
+
update();
}
@@ -1128,8 +1135,28 @@
return;
}
Slog.i(TAG, "Switching to Interactive Screen Brightness Mode");
+ ShortTermModel tempShortTermModel = new ShortTermModel();
+ tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
+ mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);
+ mHandler.removeMessages(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL);
+ // Send delayed timeout
+ mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
+ mClock.uptimeMillis()
+ + mCurrentBrightnessMapper.getShortTermModelTimeout());
+ Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel.toString());
+
+ // restore interactive mapper.
mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
- resetShortTermModel();
+
+ // if previous stm has been invalidated, and lux has drastically changed, just use
+ // the new, reset stm.
+ // if previous stm is still valid then revalidate it
+ if (!mPausedShortTermModel.maybeReset(mAmbientLux)) {
+ setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
+ mPausedShortTermModel.mBrightness);
+ }
+ mPausedShortTermModel.copyFrom(tempShortTermModel);
+
update();
}
@@ -1164,6 +1191,77 @@
}
}
+ private class ShortTermModel {
+ // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
+ // user's adjustment) immediately, but wait for a drastic enough change in the ambient
+ // light.
+ // The anchor determines what were the light levels when the user has set their preference,
+ // and we use a relative threshold to determine when to revert to the OEM curve.
+ private float mAnchor = -1f;
+ private float mBrightness;
+ private boolean mIsValid = true;
+
+ private void reset() {
+ mAnchor = -1f;
+ mBrightness = -1f;
+ mIsValid = true;
+ }
+
+ private void invalidate() {
+ mIsValid = false;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "ShortTermModel: invalidate user data");
+ }
+ }
+
+ private void setUserBrightness(float lux, float brightness) {
+ mAnchor = lux;
+ mBrightness = brightness;
+ mIsValid = true;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "ShortTermModel: anchor=" + mAnchor);
+ }
+ }
+
+ private boolean maybeReset(float currentLux) {
+ // If the short term model was invalidated and the change is drastic enough, reset it.
+ // Otherwise, we revalidate it.
+ if (!mIsValid && mAnchor != -1) {
+ if (mCurrentBrightnessMapper != null
+ && mCurrentBrightnessMapper.shouldResetShortTermModel(
+ currentLux, mAnchor)) {
+ resetShortTermModel();
+ } else {
+ mIsValid = true;
+ }
+ return mIsValid;
+ }
+ return false;
+ }
+
+ private void set(float anchor, float brightness, boolean valid) {
+ mAnchor = anchor;
+ mBrightness = brightness;
+ mIsValid = valid;
+ }
+ private void copyFrom(ShortTermModel from) {
+ mAnchor = from.mAnchor;
+ mBrightness = from.mBrightness;
+ mIsValid = from.mIsValid;
+ }
+
+ public String toString() {
+ return " mAnchor: " + mAnchor
+ + "\n mBrightness: " + mBrightness
+ + "\n mIsValid: " + mIsValid;
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(this);
+ }
+
+ }
+
private final class AutomaticBrightnessHandler extends Handler {
public AutomaticBrightnessHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -1184,8 +1282,8 @@
collectBrightnessAdjustmentSample();
break;
- case MSG_INVALIDATE_SHORT_TERM_MODEL:
- invalidateShortTermModel();
+ case MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL:
+ mShortTermModel.invalidate();
break;
case MSG_UPDATE_FOREGROUND_APP:
@@ -1195,6 +1293,10 @@
case MSG_UPDATE_FOREGROUND_APP_SYNC:
updateForegroundAppSync();
break;
+
+ case MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL:
+ mPausedShortTermModel.invalidate();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index d047183..3456e3e 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -363,13 +363,17 @@
public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment);
/**
- * Returns the timeout for the short term model
+ * Returns the timeout, in milliseconds for the short term model
*
* Timeout after which we remove the effects any user interactions might've had on the
* brightness mapping. This timeout doesn't start until we transition to a non-interactive
* display policy so that we don't reset while users are using their devices, but also so that
* we don't erroneously keep the short-term model if the device is dozing but the
* display is fully on.
+ *
+ * This timeout is also used when the device switches from interactive screen brightness mode
+ * to idle screen brightness mode, to preserve the user's preference when they resume usage of
+ * the device, within the specified timeframe.
*/
public abstract long getShortTermModelTimeout();
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index eccee52..cfdcd63 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -16,7 +16,10 @@
package com.android.server.display;
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
@@ -33,8 +36,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -63,49 +66,69 @@
private final DeviceConfigInterface mDeviceConfig;
private int mThrottlingStatus;
- private BrightnessThrottlingData mThrottlingData;
- private BrightnessThrottlingData mDdcThrottlingData;
+
+ // Maps the throttling ID to the data. Sourced from DisplayDeviceConfig.
+ @NonNull
+ private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
+
+ // Current throttling data being used.
+ // Null if we do not support throttling.
+ @Nullable
+ private ThermalBrightnessThrottlingData mThermalThrottlingData;
+
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
private String mUniqueDisplayId;
// The most recent string that has been set from DeviceConfig
- private String mBrightnessThrottlingDataString;
+ private String mThermalBrightnessThrottlingDataString;
+
+ // The brightness throttling configuration that should be used.
+ private String mThermalBrightnessThrottlingDataId;
// This is a collection of brightness throttling data that has been written as overrides from
// the DeviceConfig. This will always take priority over the display device config data.
- private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride =
- new HashMap<>(1);
+ // We need to store the data for every display device, so we do not need to update this each
+ // time the underlying display device changes.
+ // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
+ // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
+ private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
+ mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
- BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
- Runnable throttlingChangeCallback, String uniqueDisplayId) {
- this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback,
- uniqueDisplayId);
+ BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
+ String throttlingDataId,
+ @NonNull HashMap<String, ThermalBrightnessThrottlingData>
+ thermalBrightnessThrottlingDataMap) {
+ this(new Injector(), handler, handler, throttlingChangeCallback,
+ uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap);
}
@VisibleForTesting
BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
- BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback,
- String uniqueDisplayId) {
+ Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId,
+ @NonNull HashMap<String, ThermalBrightnessThrottlingData>
+ thermalBrightnessThrottlingDataMap) {
mInjector = injector;
mHandler = handler;
mDeviceConfigHandler = deviceConfigHandler;
- mThrottlingData = throttlingData;
- mDdcThrottlingData = throttlingData;
+ mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
mThrottlingChangeCallback = throttlingChangeCallback;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
mUniqueDisplayId = uniqueDisplayId;
mDeviceConfig = injector.getDeviceConfig();
mDeviceConfigListener = new DeviceConfigListener();
-
- resetThrottlingData(mThrottlingData, mUniqueDisplayId);
+ mThermalBrightnessThrottlingDataId = throttlingDataId;
+ mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
+ loadThermalBrightnessThrottlingDataFromDeviceConfig();
+ loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap,
+ mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
boolean deviceSupportsThrottling() {
- return mThrottlingData != null;
+ return mThermalThrottlingData != null;
}
float getBrightnessCap() {
@@ -133,23 +156,14 @@
mThrottlingStatus = THROTTLING_INVALID;
}
- private void resetThrottlingData() {
- resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId);
- }
-
- void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) {
- stop();
-
- mUniqueDisplayId = displayId;
- mDdcThrottlingData = throttlingData;
- mDeviceConfigListener.startListening();
- reloadBrightnessThrottlingDataOverride();
- mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId,
- throttlingData);
-
- if (deviceSupportsThrottling()) {
- mSkinThermalStatusObserver.startObserving();
- }
+ void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+ HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
+ String brightnessThrottlingDataId,
+ String uniqueDisplayId) {
+ mDdcThermalThrottlingDataMap = ddcThrottlingDataMap;
+ mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mUniqueDisplayId = uniqueDisplayId;
+ resetThermalThrottlingData();
}
private float verifyAndConstrainBrightnessCap(float brightness) {
@@ -171,11 +185,11 @@
private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
if (mThrottlingStatus != newStatus) {
mThrottlingStatus = newStatus;
- updateThrottling();
+ updateThermalThrottling();
}
}
- private void updateThrottling() {
+ private void updateThermalThrottling() {
if (!deviceSupportsThrottling()) {
return;
}
@@ -183,9 +197,9 @@
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
- if (mThrottlingStatus != THROTTLING_INVALID) {
+ if (mThrottlingStatus != THROTTLING_INVALID && mThermalThrottlingData != null) {
// Throttling levels are sorted by increasing severity
- for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
+ for (ThrottlingLevel level : mThermalThrottlingData.throttlingLevels) {
if (level.thermalStatus <= mThrottlingStatus) {
brightnessCap = level.brightness;
brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
@@ -218,27 +232,40 @@
private void dumpLocal(PrintWriter pw) {
pw.println("BrightnessThrottler:");
- pw.println(" mThrottlingData=" + mThrottlingData);
- pw.println(" mDdcThrottlingData=" + mDdcThrottlingData);
+ pw.println(" mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
+ pw.println(" mThermalThrottlingData=" + mThermalThrottlingData);
pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
pw.println(" mThrottlingStatus=" + mThrottlingStatus);
pw.println(" mBrightnessCap=" + mBrightnessCap);
pw.println(" mBrightnessMaxReason=" +
BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
- pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride);
- pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString);
+ pw.println(" mDdcThermalThrottlingDataMap=" + mDdcThermalThrottlingDataMap);
+ pw.println(" mThermalBrightnessThrottlingDataOverride="
+ + mThermalBrightnessThrottlingDataOverride);
+ pw.println(" mThermalBrightnessThrottlingDataString="
+ + mThermalBrightnessThrottlingDataString);
mSkinThermalStatusObserver.dump(pw);
}
- private String getBrightnessThrottlingDataString() {
+ private String getThermalBrightnessThrottlingDataString() {
return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
/* defaultValue= */ null);
}
- private boolean parseAndSaveData(@NonNull String strArray,
- @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) {
+ // The brightness throttling data id may or may not be specified in the string that is passed
+ // in, if there is none specified, we assume it is for the default case. Each string passed in
+ // here must be for one display and one throttling id.
+ // 123,1,critical,0.8
+ // 456,2,moderate,0.9,critical,0.7
+ // 456,2,moderate,0.9,critical,0.7,default
+ // 456,2,moderate,0.9,critical,0.7,id_2
+ // displayId, number, <state, val> * number
+ // displayId, <number, <state, val> * number>, throttlingId
+ private boolean parseAndAddData(@NonNull String strArray,
+ @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
+ displayIdToThrottlingIdToBtd) {
boolean validConfig = true;
String[] items = strArray.split(",");
int i = 0;
@@ -254,61 +281,110 @@
for (int j = 0; j < noOfThrottlingPoints; j++) {
String severity = items[i++];
int status = parseThermalStatus(severity);
-
float brightnessPoint = parseBrightness(items[i++]);
-
throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
}
- BrightnessThrottlingData toSave =
- DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels);
- tempBrightnessThrottlingData.put(uniqueDisplayId, toSave);
+
+ String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
+ ThermalBrightnessThrottlingData throttlingLevelsData =
+ DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
+
+ // Add throttlingLevelsData to inner map where necessary.
+ HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
+ displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
+ if (throttlingMapForDisplay == null) {
+ throttlingMapForDisplay = new HashMap<>();
+ throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
+ displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
+ } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
+ Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
+ + "contains duplicate throttling ids: '" + throttlingDataId + "'");
+ return false;
+ } else {
+ throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
+ }
} catch (NumberFormatException | IndexOutOfBoundsException
| UnknownThermalStatusException e) {
- validConfig = false;
Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
+ validConfig = false;
}
if (i != items.length) {
validConfig = false;
}
-
return validConfig;
}
- public void reloadBrightnessThrottlingDataOverride() {
- HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData =
+ private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
+ HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
new HashMap<>(1);
- mBrightnessThrottlingDataString = getBrightnessThrottlingDataString();
+ mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
boolean validConfig = true;
- mBrightnessThrottlingDataOverride.clear();
- if (mBrightnessThrottlingDataString != null) {
- String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";");
+ mThermalBrightnessThrottlingDataOverride.clear();
+ if (mThermalBrightnessThrottlingDataString != null) {
+ String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
for (String s : throttlingDataSplits) {
- if (!parseAndSaveData(s, tempBrightnessThrottlingData)) {
+ if (!parseAndAddData(s, tempThrottlingData)) {
validConfig = false;
break;
}
}
if (validConfig) {
- mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData);
- tempBrightnessThrottlingData.clear();
+ mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
+ tempThrottlingData.clear();
}
} else {
- Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null");
+ Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
}
}
+ private void resetThermalThrottlingData() {
+ stop();
+
+ mDeviceConfigListener.startListening();
+
+ // Get throttling data for this id, if it exists
+ mThermalThrottlingData = getConfigFromId(mThermalBrightnessThrottlingDataId);
+
+ // Fallback to default id otherwise.
+ if (!DEFAULT_ID.equals(mThermalBrightnessThrottlingDataId)
+ && mThermalThrottlingData == null) {
+ mThermalThrottlingData = getConfigFromId(DEFAULT_ID);
+ Slog.d(TAG, "Falling back to default throttling Id");
+ }
+
+ if (deviceSupportsThrottling()) {
+ mSkinThermalStatusObserver.startObserving();
+ }
+ }
+
+ private ThermalBrightnessThrottlingData getConfigFromId(String id) {
+ ThermalBrightnessThrottlingData returnValue;
+
+ // Fallback pattern for fetching correct throttling data for this display and id.
+ // 1) throttling data from device config for this throttling data id
+ returnValue = mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null
+ ? null
+ : mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id);
+ // 2) throttling data from ddc for this throttling data id
+ returnValue = returnValue == null
+ ? mDdcThermalThrottlingDataMap.get(id)
+ : returnValue;
+
+ return returnValue;
+ }
+
/**
* Listens to config data change and updates the brightness throttling data using
* DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA.
* The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe,
* 0.379518072;local:4619827677550801151,1,moderate,0.75"
* In this order:
- * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
- * Where the latter part is repeated for each throttling level, and the entirety is repeated
- * for each display, separated by a semicolon.
+ * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>][,throttlingId]?
+ * Where [<severity as string>,<brightness cap>] is repeated for each throttling level, and the
+ * entirety is repeated for each display & throttling data id, separated by a semicolon.
*/
public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
@@ -320,8 +396,8 @@
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
- reloadBrightnessThrottlingDataOverride();
- resetThrottlingData();
+ loadThermalBrightnessThrottlingDataFromDeviceConfig();
+ resetThermalThrottlingData();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f4b3f1a..a021174 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -143,17 +143,17 @@
* <brightness>0.01</brightness>
* </brightnessThrottlingPoint>
* </brightnessThrottlingMap>
- * <concurrentDisplaysBrightnessThrottlingMap>
- * <brightnessThrottlingPoint>
- * <thermalStatus>severe</thermalStatus>
- * <brightness>0.07</brightness>
- * </brightnessThrottlingPoint>
- * <brightnessThrottlingPoint>
- * <thermalStatus>critical</thermalStatus>
- * <brightness>0.005</brightness>
- * </brightnessThrottlingPoint>
- * </concurrentDisplaysBrightnessThrottlingMap>
- * <refreshRateThrottlingMap>
+ * <brightnessThrottlingMap id="id_2"> // optional attribute, leave blank for default
+ * <brightnessThrottlingPoint>
+ * <thermalStatus>moderate</thermalStatus>
+ * <brightness>0.2</brightness>
+ * </brightnessThrottlingPoint>
+ * <brightnessThrottlingPoint>
+ * <thermalStatus>severe</thermalStatus>
+ * <brightness>0.1</brightness>
+ * </brightnessThrottlingPoint>
+ * </brightnessThrottlingMap>
+ <refreshRateThrottlingMap>
* <refreshRateThrottlingPoint>
* <thermalStatus>critical</thermalStatus>
* <refreshRateRange>
@@ -687,8 +687,8 @@
private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
- private final Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap =
- new HashMap<>();
+ private final HashMap<String, ThermalBrightnessThrottlingData>
+ mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
mRefreshRateThrottlingMap = new HashMap<>();
@@ -1346,11 +1346,11 @@
}
/**
- * @param id The ID of the throttling data
- * @return brightness throttling configuration data for the display.
+ * @return brightness throttling configuration data for this display, for each throttling id.
*/
- public BrightnessThrottlingData getBrightnessThrottlingData(String id) {
- return BrightnessThrottlingData.create(mBrightnessThrottlingDataMap.get(id));
+ public HashMap<String, ThermalBrightnessThrottlingData>
+ getThermalBrightnessThrottlingDataMapByThrottlingId() {
+ return mThermalBrightnessThrottlingDataMapByThrottlingId;
}
/**
@@ -1358,7 +1358,7 @@
* @return refresh rate throttling configuration
*/
@Nullable
- public SparseArray<SurfaceControl.RefreshRateRange> getRefreshRateThrottlingData(
+ public SparseArray<SurfaceControl.RefreshRateRange> getThermalRefreshRateThrottlingData(
@Nullable String id) {
String key = id == null ? DEFAULT_ID : id;
return mRefreshRateThrottlingMap.get(key);
@@ -1525,7 +1525,8 @@
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
- + ", mBrightnessThrottlingData=" + mBrightnessThrottlingDataMap
+ + ", mThermalBrightnessThrottlingDataMapByThrottlingId="
+ + mThermalBrightnessThrottlingDataMapByThrottlingId
+ "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
@@ -1886,11 +1887,11 @@
Slog.i(TAG, "No thermal throttling config found");
return;
}
- loadBrightnessThrottlingMaps(throttlingConfig);
- loadRefreshRateThermalThrottlingMap(throttlingConfig);
+ loadThermalBrightnessThrottlingMaps(throttlingConfig);
+ loadThermalRefreshRateThrottlingMap(throttlingConfig);
}
- private void loadBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) {
+ private void loadThermalBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) {
final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap();
if (maps == null || maps.isEmpty()) {
Slog.i(TAG, "No brightness throttling map found");
@@ -1900,7 +1901,7 @@
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 =
+ List<ThermalBrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
new ArrayList<>(points.size());
boolean badConfig = false;
@@ -1911,24 +1912,24 @@
break;
}
- throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+ throttlingLevels.add(new ThermalBrightnessThrottlingData.ThrottlingLevel(
convertThermalStatus(status), point.getBrightness().floatValue()));
}
if (!badConfig) {
String id = map.getId() == null ? DEFAULT_ID
: map.getId();
- if (mBrightnessThrottlingDataMap.containsKey(id)) {
+ if (mThermalBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) {
throw new RuntimeException("Brightness throttling data with ID " + id
+ " already exists");
}
- mBrightnessThrottlingDataMap.put(id,
- BrightnessThrottlingData.create(throttlingLevels));
+ mThermalBrightnessThrottlingDataMapByThrottlingId.put(id,
+ ThermalBrightnessThrottlingData.create(throttlingLevels));
}
}
}
- private void loadRefreshRateThermalThrottlingMap(ThermalThrottling throttlingConfig) {
+ private void loadThermalRefreshRateThrottlingMap(ThermalThrottling throttlingConfig) {
List<RefreshRateThrottlingMap> maps = throttlingConfig.getRefreshRateThrottlingMap();
if (maps == null || maps.isEmpty()) {
Slog.w(TAG, "RefreshRateThrottling: map not found");
@@ -1971,8 +1972,8 @@
));
}
if (refreshRates.size() == 0) {
- Slog.w(TAG, "RefreshRateThrottling: no valid throttling points fond for map, mapId="
- + id);
+ Slog.w(TAG, "RefreshRateThrottling: no valid throttling points found for map, "
+ + "mapId=" + id);
continue;
}
mRefreshRateThrottlingMap.put(id, refreshRates);
@@ -3038,7 +3039,7 @@
/**
* Container for brightness throttling data.
*/
- public static class BrightnessThrottlingData {
+ public static class ThermalBrightnessThrottlingData {
public List<ThrottlingLevel> throttlingLevels;
static class ThrottlingLevel {
@@ -3077,9 +3078,10 @@
/**
- * Creates multiple teperature based throttling levels of brightness
+ * Creates multiple temperature based throttling levels of brightness
*/
- public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) {
+ public static ThermalBrightnessThrottlingData create(
+ List<ThrottlingLevel> throttlingLevels) {
if (throttlingLevels == null || throttlingLevels.size() == 0) {
Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
return null;
@@ -3117,21 +3119,12 @@
}
}
- return new BrightnessThrottlingData(throttlingLevels);
+ return new ThermalBrightnessThrottlingData(throttlingLevels);
}
- static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
- if (other == null) {
- return null;
- }
-
- return BrightnessThrottlingData.create(other.throttlingLevels);
- }
-
-
@Override
public String toString() {
- return "BrightnessThrottlingData{"
+ return "ThermalBrightnessThrottlingData{"
+ "throttlingLevels:" + throttlingLevels
+ "} ";
}
@@ -3142,12 +3135,12 @@
return true;
}
- if (!(obj instanceof BrightnessThrottlingData)) {
+ if (!(obj instanceof ThermalBrightnessThrottlingData)) {
return false;
}
- BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj;
- return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels);
+ ThermalBrightnessThrottlingData otherData = (ThermalBrightnessThrottlingData) obj;
+ return throttlingLevels.equals(otherData.throttlingLevels);
}
@Override
@@ -3156,7 +3149,7 @@
}
@VisibleForTesting
- BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
+ ThermalBrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
throttlingLevels = new ArrayList<>(inLevels.size());
for (ThrottlingLevel level : inLevels) {
throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c3a4c2e..85b4034 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1418,19 +1418,32 @@
flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
}
- if (projection != null) {
- final long firstToken = Binder.clearCallingIdentity();
- try {
+ // Check if the host app is attempting to reuse the token or capture again on the same
+ // MediaProjection instance. Don't start recording if so; MediaProjectionManagerService
+ // decides how to respond based on the target SDK.
+ boolean waitForPermissionConsent = false;
+ final long firstToken = Binder.clearCallingIdentity();
+ try {
+ if (projection != null) {
if (!getProjectionService().isCurrentProjection(projection)) {
throw new SecurityException("Cannot create VirtualDisplay with "
+ "non-current MediaProjection");
}
+ if (!projection.isValid()) {
+ // Just log; MediaProjectionManagerService throws an exception.
+ Slog.w(TAG, "Reusing token: create virtual display for app reusing token");
+ // If the exception wasn't thrown, we continue and re-show the permission dialog
+ getProjectionService().requestConsentForInvalidProjection(projection);
+ // Declare that mirroring shouldn't begin until user reviews the permission
+ // dialog.
+ waitForPermissionConsent = true;
+ }
flags = projection.applyVirtualDisplayFlags(flags);
- } catch (RemoteException e) {
- throw new SecurityException("unable to validate media projection or flags");
- } finally {
- Binder.restoreCallingIdentity(firstToken);
}
+ } catch (RemoteException e) {
+ throw new SecurityException("Unable to validate media projection or flags", e);
+ } finally {
+ Binder.restoreCallingIdentity(firstToken);
}
if (callingUid != Process.SYSTEM_UID
@@ -1548,22 +1561,28 @@
// Only attempt to set content recording session if there are details to set and a
// VirtualDisplay has been successfully constructed.
session.setVirtualDisplayId(displayId);
+ // Don't start mirroring until user re-grants consent.
+ session.setWaitingToRecord(waitForPermissionConsent);
// We set the content recording session here on the server side instead of using
// a second AIDL call in MediaProjection. By ensuring that a virtual display has
// been constructed before calling setContentRecordingSession, we avoid a race
// condition between the DisplayManagerService & WindowManagerService which could
// lead to the MediaProjection being pre-emptively torn down.
- if (!mWindowManagerInternal.setContentRecordingSession(session)) {
- // Unable to start mirroring, so tear down projection & release VirtualDisplay.
- try {
- getProjectionService().stopActiveProjection();
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the "
- + "active projection", e);
+ try {
+ if (!getProjectionService().setContentRecordingSession(session, projection)) {
+ // Unable to start mirroring, so release VirtualDisplay. Projection service
+ // handles stopping the projection.
+ releaseVirtualDisplayInternal(callback.asBinder());
+ return Display.INVALID_DISPLAY;
+ } else if (projection != null) {
+ // Indicate that this projection has been used to record, and can't be used
+ // again.
+ projection.notifyVirtualDisplayCreated(displayId);
}
- releaseVirtualDisplayInternal(callback.asBinder());
- return Display.INVALID_DISPLAY;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
+ + "content recording session", e);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f5859ee..5e3990a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -505,7 +505,7 @@
private DisplayDeviceConfig mDisplayDeviceConfig;
- // Identifiers for suspend blocker acuisition requests
+ // Identifiers for suspend blocker acquisition requests
private final String mSuspendBlockerIdUnfinishedBusiness;
private final String mSuspendBlockerIdOnStateChanged;
private final String mSuspendBlockerIdProxPositive;
@@ -515,7 +515,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
- private String mBrightnessThrottlingDataId;
+ // The id of the thermal brightness throttling policy that should be used.
+ private String mThermalBrightnessThrottlingDataId;
// 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.
@@ -555,7 +556,8 @@
mHandler = new DisplayControllerHandler(handler.getLooper());
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
- mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
+ mThermalBrightnessThrottlingDataId =
+ logicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mBatteryStats = BatteryStatsService.getService();
@@ -890,8 +892,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
- final String brightnessThrottlingDataId =
- mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
+ final String thermalBrightnessThrottlingDataId =
+ mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
mHandler.postAtTime(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -900,19 +902,21 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
loadNitBasedBrightnessSetting();
/// 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
+ // last command that was sent to change it's state. Let's assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
- } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ } else if (
+ !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) {
changed = true;
- mBrightnessThrottlingDataId = brightnessThrottlingDataId;
- mBrightnessThrottler.resetThrottlingData(
- config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
+ mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+ config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mThermalBrightnessThrottlingDataId,
mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -981,9 +985,9 @@
sdrBrightness, maxDesiredHdrSdrRatio);
}
});
- mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
- mUniqueDisplayId);
+ mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+ mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -2116,11 +2120,11 @@
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
return new BrightnessThrottler(mHandler,
- ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
- }, mUniqueDisplayId);
+ }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(),
+ ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
}
private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 8ce4b66..23e606c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -400,7 +400,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
- private String mBrightnessThrottlingDataId;
+ // The id of the thermal brightness throttling policy that should be used.
+ private String mThermalBrightnessThrottlingDataId;
// 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.
@@ -438,7 +439,8 @@
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
- mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
+ mThermalBrightnessThrottlingDataId =
+ logicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -706,8 +708,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
- final String brightnessThrottlingDataId =
- mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
+ final String thermalBrightnessThrottlingDataId =
+ mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
mHandler.postAtTime(() -> {
boolean changed = false;
@@ -717,19 +719,21 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
// 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
+ // last command that was sent to change it's state. Let's assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
- } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ } else if (
+ !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) {
changed = true;
- mBrightnessThrottlingDataId = brightnessThrottlingDataId;
- mBrightnessThrottler.resetThrottlingData(
- config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
+ mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+ config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mThermalBrightnessThrottlingDataId,
mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -795,9 +799,9 @@
sdrBrightness, maxDesiredHdrSdrRatio);
}
});
- mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
- mUniqueDisplayId);
+ mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+ mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -1757,11 +1761,11 @@
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
return new BrightnessThrottler(mHandler,
- ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
- }, mUniqueDisplayId);
+ }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(),
+ ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
}
private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dee4cde..dab00d8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -175,11 +175,11 @@
private boolean mDirty = false;
/**
- * 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.
+ * The ID of the thermal 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;
+ private String mThermalBrightnessThrottlingDataId;
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
@@ -189,7 +189,7 @@
mTempFrameRateOverride = new SparseArray<>();
mIsEnabled = true;
mIsInTransition = false;
- mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+ mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
}
public void setDevicePositionLocked(int position) {
@@ -349,7 +349,7 @@
*
* @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default
*/
- public void updateRefreshRateThermalThrottling(
+ public void updateThermalRefreshRateThrottling(
@Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) {
if (refreshRanges == null) {
refreshRanges = new SparseArray<>();
@@ -872,16 +872,16 @@
/**
* @return The ID of the brightness throttling data that this display should use.
*/
- public String getBrightnessThrottlingDataIdLocked() {
- return mBrightnessThrottlingDataId;
+ public String getThermalBrightnessThrottlingDataIdLocked() {
+ return mThermalBrightnessThrottlingDataId;
}
/**
* @param brightnessThrottlingDataId The ID of the brightness throttling data that this
* display should use.
*/
- public void setBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
- mBrightnessThrottlingDataId =
+ public void setThermalBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
+ mThermalBrightnessThrottlingDataId =
brightnessThrottlingDataId;
}
@@ -950,7 +950,7 @@
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
pw.println("mDisplayGroupName=" + mDisplayGroupName);
- pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
+ pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
pw.println("mLeadDisplayId=" + mLeadDisplayId);
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 424eedc..254441c2 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1022,17 +1022,17 @@
newDisplay.updateLayoutLimitedRefreshRateLocked(
config.getRefreshRange(displayLayout.getRefreshRateZoneId())
);
- newDisplay.updateRefreshRateThermalThrottling(
- config.getRefreshRateThrottlingData(
+ newDisplay.updateThermalRefreshRateThrottling(
+ config.getThermalRefreshRateThrottlingData(
displayLayout.getRefreshRateThermalThrottlingMapId()
)
);
setEnabledLocked(newDisplay, displayLayout.isEnabled());
- newDisplay.setBrightnessThrottlingDataIdLocked(
- displayLayout.getBrightnessThrottlingMapId() == null
+ newDisplay.setThermalBrightnessThrottlingDataIdLocked(
+ displayLayout.getThermalBrightnessThrottlingMapId() == null
? DisplayDeviceConfig.DEFAULT_ID
- : displayLayout.getBrightnessThrottlingMapId());
+ : displayLayout.getThermalBrightnessThrottlingMapId());
newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
}
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 f86ee24..b55d7d5 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -234,11 +234,11 @@
// {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
private final int mPosition;
- // 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.
+ // The ID of the thermal 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;
+ private final String mThermalBrightnessThrottlingMapId;
// The ID of the lead display that this display will follow in a layout. -1 means no lead.
private final int mLeadDisplayId;
@@ -248,7 +248,7 @@
private final String mRefreshRateZoneId;
@Nullable
- private final String mRefreshRateThermalThrottlingMapId;
+ private final String mThermalRefreshRateThrottlingMapId;
private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
@@ -259,9 +259,9 @@
mIsEnabled = isEnabled;
mDisplayGroupName = displayGroupName;
mPosition = position;
- mBrightnessThrottlingMapId = brightnessThrottlingMapId;
+ mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
mRefreshRateZoneId = refreshRateZoneId;
- mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId;
+ mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
mLeadDisplayId = leadDisplayId;
}
@@ -273,10 +273,10 @@
+ ", displayGroupName: " + mDisplayGroupName
+ ", addr: " + mAddress
+ ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
- + ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
+ ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ ", mLeadDisplayId: " + mLeadDisplayId
- + ", mRefreshRateThermalThrottlingMapId: " + mRefreshRateThermalThrottlingMapId
+ + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+ "}";
}
@@ -293,12 +293,12 @@
&& otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
&& this.mDisplayGroupName.equals(otherDisplay.mDisplayGroupName)
&& this.mAddress.equals(otherDisplay.mAddress)
- && Objects.equals(mBrightnessThrottlingMapId,
- otherDisplay.mBrightnessThrottlingMapId)
+ && Objects.equals(mThermalBrightnessThrottlingMapId,
+ otherDisplay.mThermalBrightnessThrottlingMapId)
&& Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
- && Objects.equals(mRefreshRateThermalThrottlingMapId,
- otherDisplay.mRefreshRateThermalThrottlingMapId);
+ && Objects.equals(mThermalRefreshRateThrottlingMapId,
+ otherDisplay.mThermalRefreshRateThrottlingMapId);
}
@Override
@@ -309,10 +309,10 @@
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mDisplayGroupName.hashCode();
result = 31 * result + mAddress.hashCode();
- result = 31 * result + mBrightnessThrottlingMapId.hashCode();
+ result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
result = 31 * result + mLeadDisplayId;
- result = 31 * result + Objects.hashCode(mRefreshRateThermalThrottlingMapId);
+ result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
return result;
}
@@ -338,10 +338,12 @@
}
/**
- * @return The ID of the brightness throttling map that this display should use.
+ * Gets the id of the thermal brightness throttling map that should be used.
+ * @return The ID of the thermal brightness throttling map that this display should use,
+ * null if unspecified, will fall back to default.
*/
- public String getBrightnessThrottlingMapId() {
- return mBrightnessThrottlingMapId;
+ public String getThermalBrightnessThrottlingMapId() {
+ return mThermalBrightnessThrottlingMapId;
}
/**
@@ -359,7 +361,7 @@
}
public String getRefreshRateThermalThrottlingMapId() {
- return mRefreshRateThermalThrottlingMapId;
+ return mThermalRefreshRateThrottlingMapId;
}
}
}
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index f93d9ee..c04735d 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -102,7 +102,7 @@
//region DisplayManager.DisplayListener
@Override
public void onDisplayAdded(int displayId) {
- updateRefreshRateThermalThrottling(displayId);
+ updateThermalRefreshRateThrottling(displayId);
if (mLoggingEnabled) {
Slog.d(TAG, "Display added:" + displayId);
}
@@ -122,7 +122,7 @@
@Override
public void onDisplayChanged(int displayId) {
- updateRefreshRateThermalThrottling(displayId);
+ updateThermalRefreshRateThrottling(displayId);
if (mLoggingEnabled) {
Slog.d(TAG, "Display changed:" + displayId);
}
@@ -150,7 +150,7 @@
}
}
- private void updateRefreshRateThermalThrottling(int displayId) {
+ private void updateThermalRefreshRateThrottling(int displayId) {
DisplayInfo displayInfo = new DisplayInfo();
mInjector.getDisplayInfo(displayId, displayInfo);
SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index bb1a445..effef47 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -37,6 +37,7 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -273,6 +274,9 @@
}
mHandwritingSurface.startIntercepting(imePid, imeUid);
+ // Unset the pointer icon for the stylus in case the app had set it.
+ InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+
return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
mHandwritingBuffer);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c212e8e..44ae454 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -19,7 +19,6 @@
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
-import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -66,8 +65,8 @@
private boolean mShowImeWithHardKeyboard;
@GuardedBy("ImfLock.class")
- @Nullable
- private InputMethodDialogWindowContext mDialogWindowContext;
+ private final InputMethodDialogWindowContext mDialogWindowContext =
+ new InputMethodDialogWindowContext();
InputMethodMenuController(InputMethodManagerService service) {
mService = service;
@@ -125,13 +124,11 @@
}
}
- if (mDialogWindowContext == null) {
- mDialogWindowContext = new InputMethodDialogWindowContext();
- }
final Context dialogWindowContext = mDialogWindowContext.get(displayId);
mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
+ // TODO(b/277061090): refactor UI components should not be created while holding a lock.
final Context dialogContext = mDialogBuilder.getContext();
final TypedArray a = dialogContext.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
@@ -199,10 +196,11 @@
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
+ // TODO(b/277062834) decouple/remove dependency on IMMS
mService.updateSystemUiLocked();
mService.sendOnNavButtonFlagsChangedLocked();
- mSwitchingDialog.show();
}
+ mSwitchingDialog.show();
}
private boolean isScreenLocked() {
@@ -276,6 +274,7 @@
private final int mTextViewResourceId;
private final List<ImeSubtypeListItem> mItemsList;
public int mCheckedItem;
+
private ImeSubtypeListAdapter(Context context, int textViewResourceId,
List<ImeSubtypeListItem> itemsList, int checkedItem) {
super(context, textViewResourceId, itemsList);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 48acc7c..f0e8ede 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.view.Display.INVALID_DISPLAY;
import android.Manifest;
import android.annotation.NonNull;
@@ -26,10 +27,14 @@
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IProcessObserver;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.hardware.display.DisplayManager;
@@ -46,11 +51,13 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
@@ -60,6 +67,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.time.Duration;
import java.util.Map;
/**
@@ -75,14 +83,29 @@
private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = true;
private static final String TAG = "MediaProjectionManagerService";
+ /**
+ * Determines how to respond to an app re-using a consent token; either failing or allowing the
+ * user to re-grant consent.
+ *
+ * <p>Enabled after version 33 (Android T), so applies to target SDK of 34+ (Android U+).
+ * @hide
+ */
+ @VisibleForTesting
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long MEDIA_PROJECTION_PREVENTS_REUSING_CONSENT = 266201607L; // buganizer id
+
private final Object mLock = new Object(); // Protects the list of media projections
private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
private final CallbackDelegate mCallbackDelegate;
private final Context mContext;
+ private final Injector mInjector;
+ private final Clock mClock;
private final AppOpsManager mAppOps;
private final ActivityManagerInternal mActivityManagerInternal;
private final PackageManager mPackageManager;
+ private final WindowManagerInternal mWmInternal;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -92,18 +115,53 @@
private MediaProjection mProjectionGrant;
public MediaProjectionManagerService(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) {
super(context);
mContext = context;
+ mInjector = injector;
+ mClock = injector.createClock();
mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
mCallbackDelegate = new CallbackDelegate();
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManager = mContext.getPackageManager();
+ mWmInternal = LocalServices.getService(WindowManagerInternal.class);
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
Watchdog.getInstance().addMonitor(this);
}
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+
+ /**
+ * Returns whether we should prevent the calling app from re-using the user's consent, or
+ * allow the user to re-grant access to the same consent token.
+ */
+ boolean shouldMediaProjectionPreventReusingConsent(MediaProjection projection) {
+ // TODO(b/269273190): query feature flag directly instead of injecting.
+ return CompatChanges.isChangeEnabled(MEDIA_PROJECTION_PREVENTS_REUSING_CONSENT,
+ projection.packageName, UserHandle.getUserHandleForUid(projection.uid));
+ }
+
+ Clock createClock() {
+ return SystemClock::uptimeMillis;
+ }
+ }
+
+
@Override
public void onStart() {
publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
@@ -232,7 +290,31 @@
mCallbackDelegate.dispatchStop(projection);
}
- private boolean isCurrentProjection(IBinder token) {
+ /**
+ * Returns {@code true} when updating the current mirroring session on WM succeeded, and
+ * {@code false} otherwise.
+ */
+ @VisibleForTesting
+ boolean setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
+ synchronized (mLock) {
+ if (!mWmInternal.setContentRecordingSession(
+ incomingSession)) {
+ // Unable to start mirroring, so tear down this projection.
+ if (mProjectionGrant != null) {
+ mProjectionGrant.stop();
+ }
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Returns {@code true} when the given token matches the token of the current projection
+ * instance. Returns {@code false} otherwise.
+ */
+ @VisibleForTesting
+ boolean isCurrentProjection(IBinder token) {
synchronized (mLock) {
if (mProjectionToken != null) {
return mProjectionToken.equals(token);
@@ -241,7 +323,52 @@
}
}
- private MediaProjectionInfo getActiveProjectionInfo() {
+
+ /**
+ * Reshows the permisison dialog for the user to review consent they've already granted in
+ * the given projection instance.
+ *
+ * <p>Preconditions:
+ * <ul>
+ * <li>{@link IMediaProjection#isValid} returned false, rather than throwing an exception</li>
+ * <li>Given projection instance is the current projection instance.</li>
+ * <ul>
+ *
+ * <p>Returns immediately but waits to start recording until user has reviewed their consent.
+ */
+ @VisibleForTesting
+ void requestConsentForInvalidProjection(IMediaProjection projection) {
+ synchronized (mLock) {
+ Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
+ // TODO(b/274790702): Trigger the permission dialog again in SysUI.
+ }
+ }
+
+ // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
+ @VisibleForTesting
+ MediaProjection createProjectionInternal(int uid, String packageName, int type,
+ boolean isPermanentGrant, UserHandle callingUser,
+ boolean packageAttemptedReusingGrantedConsent) {
+ MediaProjection projection;
+ ApplicationInfo ai;
+ try {
+ ai = mPackageManager.getApplicationInfoAsUser(packageName, ApplicationInfoFlags.of(0),
+ callingUser);
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("No package matching :" + packageName);
+ }
+
+ projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
+ ai.isPrivilegedApp());
+ if (isPermanentGrant) {
+ mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+ projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ return projection;
+ }
+
+ @VisibleForTesting
+ MediaProjectionInfo getActiveProjectionInfo() {
synchronized (mLock) {
if (mProjectionGrant == null) {
return null;
@@ -291,22 +418,12 @@
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
}
- final ApplicationInfo ai;
- try {
- ai = mPackageManager.getApplicationInfo(packageName, 0);
- } catch (NameNotFoundException e) {
- throw new IllegalArgumentException("No package matching :" + packageName);
- }
-
MediaProjection projection;
+ final UserHandle callingUser = Binder.getCallingUserHandle();
final long callingToken = Binder.clearCallingIdentity();
try {
- projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
- ai.isPrivilegedApp());
- if (isPermanentGrant) {
- mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
- projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
- }
+ projection = createProjectionInternal(uid, packageName, type, isPermanentGrant,
+ callingUser, false);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
@@ -426,33 +543,49 @@
}
}
- /**
- * Updates the current content mirroring session.
- */
@Override
- public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession,
+ public boolean setContentRecordingSession(@Nullable ContentRecordingSession incomingSession,
@NonNull IMediaProjection projection) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to set session "
+ + "details.");
+ }
+ if (!isCurrentProjection(projection)) {
+ throw new SecurityException("Unable to set ContentRecordingSession on "
+ + "non-current MediaProjection");
+ }
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (!isCurrentProjection(projection)) {
- throw new SecurityException("Unable to set ContentRecordingSession on "
- + "non-current MediaProjection");
- }
- if (!LocalServices.getService(
- WindowManagerInternal.class).setContentRecordingSession(
- incomingSession)) {
- // Unable to start mirroring, so tear down this projection.
- if (mProjectionGrant != null) {
- mProjectionGrant.stop();
- }
- }
- }
+ return MediaProjectionManagerService.this.setContentRecordingSession(
+ incomingSession);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ @Override
+ public void requestConsentForInvalidProjection(IMediaProjection projection) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
+ + "projection is valid.");
+ }
+ if (!isCurrentProjection(projection)) {
+ Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
+ + "isn't current");
+ return;
+ }
+
+ // Remove calling app identity before performing any privileged operations.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.requestConsentForInvalidProjection(projection);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -471,7 +604,14 @@
}
}
- private final class MediaProjection extends IMediaProjection.Stub {
+ @VisibleForTesting
+ final class MediaProjection extends IMediaProjection.Stub {
+ // Host app has 5 minutes to begin using the token before it is invalid.
+ // Some apps show a dialog for the user to interact with (selecting recording resolution)
+ // before starting capture, but after requesting consent.
+ final long mDefaultTimeoutMs = Duration.ofMinutes(5).toMillis();
+ // The creation timestamp in milliseconds, measured by {@link SystemClock#uptimeMillis}.
+ private final long mCreateTimeMs;
public final int uid;
public final String packageName;
public final UserHandle userHandle;
@@ -485,6 +625,15 @@
private boolean mRestoreSystemAlertWindow;
private IBinder mLaunchCookie = null;
+ // Values for tracking token validity.
+ // Timeout value to compare creation time against.
+ private long mTimeoutMs = mDefaultTimeoutMs;
+ // Count of number of times IMediaProjection#start is invoked.
+ private int mCountStarts = 0;
+ // Set if MediaProjection#createVirtualDisplay has been invoked previously (it
+ // should only be called once).
+ private int mVirtualDisplayId = INVALID_DISPLAY;
+
MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
boolean isPrivileged) {
mType = type;
@@ -493,6 +642,7 @@
userHandle = new UserHandle(UserHandle.getUserId(uid));
mTargetSdkVersion = targetSdkVersion;
mIsPrivileged = isPrivileged;
+ mCreateTimeMs = mClock.uptimeMillis();
// TODO(b/267740338): Add unit test.
mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(),
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
@@ -554,6 +704,9 @@
if (isCurrentProjection(asBinder())) {
Slog.w(TAG, "UID " + Binder.getCallingUid()
+ " attempted to start already started MediaProjection");
+ // It is possible the app didn't explicitly invoke stop before trying to start
+ // again; ensure this start is counted in case they are re-using this token.
+ mCountStarts++;
return;
}
@@ -612,6 +765,8 @@
}
}
startProjectionLocked(this);
+ // Mark this token as used when the app gets the MediaProjection instance.
+ mCountStarts++;
}
}
@@ -689,6 +844,51 @@
return mLaunchCookie;
}
+ @Override
+ public boolean isValid() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if this"
+ + "projection is valid.");
+ }
+ synchronized (mLock) {
+ final long curMs = mClock.uptimeMillis();
+ final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs;
+ final boolean virtualDisplayCreated = mVirtualDisplayId != INVALID_DISPLAY;
+ final boolean isValid =
+ !hasTimedOut && (mCountStarts <= 1) && !virtualDisplayCreated;
+ if (isValid) {
+ return true;
+ }
+
+ // Can safely use mProjectionGrant since we know this is the current projection.
+ if (mInjector.shouldMediaProjectionPreventReusingConsent(mProjectionGrant)) {
+ Slog.v(TAG, "Reusing token: Throw exception due to invalid projection.");
+ // Tear down projection here; necessary to ensure (among other reasons) that
+ // stop is dispatched to client and cast icon disappears from status bar.
+ mProjectionGrant.stop();
+ throw new IllegalStateException("Don't re-use the resultData to retrieve "
+ + "the same projection instance, and don't use a token that has "
+ + "timed out. Don't take multiple captures by invoking "
+ + "MediaProjection#createVirtualDisplay multiple times on the "
+ + "same instance.");
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void notifyVirtualDisplayCreated(int displayId) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to notify virtual "
+ + "display created.");
+ }
+ synchronized (mLock) {
+ mVirtualDisplayId = displayId;
+ }
+ }
+
public MediaProjectionInfo getProjectionInfo() {
return new MediaProjectionInfo(packageName, userHandle);
}
@@ -804,7 +1004,7 @@
return;
}
synchronized (mLock) {
- // TODO(b/249827847) Currently the service assumes there is only one projection
+ // TODO(b/249827847): Currently the service assumes there is only one projection
// at once - need to find the callback for the given projection, when there are
// multiple sessions.
for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
@@ -832,7 +1032,7 @@
return;
}
synchronized (mLock) {
- // TODO(b/249827847) Currently the service assumes there is only one projection
+ // TODO(b/249827847): Currently the service assumes there is only one projection
// at once - need to find the callback for the given projection, when there are
// multiple sessions.
for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4c36b91..a3d89e7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -153,6 +153,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.UidObserver;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -1117,7 +1118,7 @@
}
}
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ final private IUidObserver mUidObserver = new UidObserver() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@ProcessCapability int capability) {
synchronized (mUidStateCallbackInfos) {
@@ -1139,18 +1140,6 @@
@Override public void onUidGone(int uid, boolean disabled) {
mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
}
-
- @Override public void onUidActive(int uid) {
- }
-
- @Override public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override public void onUidProcAdjChanged(int uid) {
- }
};
private static final class UidStateCallbackInfo {
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index d3dea0d..b8900d7 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -16,7 +16,6 @@
package com.android.server.notification;
import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationChannel.ALLOW_BUBBLE_OFF;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
@@ -81,7 +80,7 @@
&& !mActivityManager.isLowRamDevice()
&& record.isConversation()
&& record.getShortcutInfo() != null
- && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0;
+ && !record.getNotification().isFgsOrUij();
boolean userEnabledBubbles = mConfig.bubblesEnabled(record.getUser());
int appPreference =
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 6f0903c..446c4f7 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -165,7 +165,7 @@
if (isCallStyle(record)) {
return true;
}
- if (!isOngoing(record)) {
+ if (!record.getNotification().isFgsOrUij()) {
return false;
}
return isCallCategory(record) || isMediaNotification(record);
@@ -199,11 +199,6 @@
return false;
}
- private boolean isOngoing(NotificationRecord record) {
- final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
- return (record.getNotification().flags & ongoingFlags) != 0;
- }
-
private boolean isMediaNotification(NotificationRecord record) {
return record.getNotification().isMediaNotification();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index bc38856..919fc71 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -35,6 +35,8 @@
void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
+ void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId, int userId);
+
void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
/** Get the number of notification channels for a given package */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2d806d3..6d27fe0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -21,6 +21,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
@@ -29,6 +30,7 @@
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
@@ -163,6 +165,7 @@
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UriGrantsManager;
@@ -304,6 +307,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
@@ -1156,8 +1160,8 @@
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
sbn.getId(), Notification.FLAG_AUTO_CANCEL,
- FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
- REASON_CLICK, nv.rank, nv.count, null);
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
+ false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
reportUserInteraction(r);
mAssistants.notifyAssistantNotificationClicked(r);
@@ -1265,21 +1269,26 @@
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
int id, int uid, int initialPid, String message, int userId) {
final boolean fgService;
+ final boolean uiJob;
synchronized (mNotificationLock) {
NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
+ uiJob = r != null && (r.getNotification().flags & FLAG_USER_INITIATED_JOB) != 0;
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
REASON_ERROR, null);
- if (fgService) {
- // Still crash for foreground services, preventing the not-crash behaviour abused
- // by apps to give us a garbage notification and silently start a fg service.
+ if (fgService || uiJob) {
+ // Still crash for foreground services or user-initiated jobs, preventing the
+ // not-crash behaviour abused by apps to give us a garbage notification and
+ // silently start a fg service or user-initiated job.
+ final int exceptionTypeId = fgService
+ ? BadForegroundServiceNotificationException.TYPE_ID
+ : BadUserInitiatedJobNotificationException.TYPE_ID;
Binder.withCleanCallingIdentity(
() -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1,
"Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
- + message, true /* force */,
- BadForegroundServiceNotificationException.TYPE_ID));
+ + message, true /* force */, exceptionTypeId));
}
}
@@ -1689,8 +1698,8 @@
cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(),
record.getSbn().getPackageName(), record.getSbn().getTag(),
record.getSbn().getId(), 0,
- FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
- REASON_TIMEOUT, null);
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+ true, record.getUserId(), REASON_TIMEOUT, null);
}
}
}
@@ -3296,8 +3305,8 @@
@Nullable ITransientNotification callback, int duration, boolean isUiContext,
int displayId, @Nullable ITransientNotificationCallback textCallback) {
if (DBG) {
- Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token
- + " duration=" + duration + " displayId=" + displayId);
+ Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration
+ + " isUiContext=" + isUiContext + " displayId=" + displayId);
}
if (pkg == null || (text == null && callback == null)
@@ -3308,15 +3317,6 @@
}
final int callingUid = Binder.getCallingUid();
- checkCallerIsSameApp(pkg);
- final boolean isSystemToast = isCallerSystemOrPhone()
- || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
- boolean isAppRenderedToast = (callback != null);
- if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
- isSystemToast)) {
- return;
- }
-
if (!isUiContext && displayId == Display.DEFAULT_DISPLAY
&& mUm.isVisibleBackgroundUsersSupported()) {
// When the caller is a visible background user using a non-UI context (like the
@@ -3333,6 +3333,15 @@
}
}
+ checkCallerIsSameApp(pkg);
+ final boolean isSystemToast = isCallerSystemOrPhone()
+ || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
+ boolean isAppRenderedToast = (callback != null);
+ if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
+ isSystemToast)) {
+ return;
+ }
+
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
final long callingId = Binder.clearCallingIdentity();
@@ -3519,10 +3528,10 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
- // Don't allow the app to cancel active FGS notifications
+ // Don't allow the app to cancel active FGS or UIJ notifications
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
- pkg, null, 0, FLAG_FOREGROUND_SERVICE, true, userId,
- REASON_APP_CANCEL_ALL, null);
+ pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+ true, userId, REASON_APP_CANCEL_ALL, null);
}
@Override
@@ -3964,6 +3973,21 @@
}
}
+ // Throws a security exception if the given channel has a notification associated
+ // with an active user-initiated job.
+ private void enforceDeletingChannelHasNoUserInitiatedJob(String pkg, int userId,
+ String channelId) {
+ final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+ if (js != null && js.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+ channelId, userId, pkg)) {
+ Slog.w(TAG, "Package u" + userId + "/" + pkg
+ + " may not delete notification channel '"
+ + channelId + "' with user-initiated job");
+ throw new SecurityException("Not allowed to delete channel " + channelId
+ + " with a user-initiated job");
+ }
+ }
+
@Override
public void deleteNotificationChannel(String pkg, String channelId) {
checkCallerIsSystemOrSameApp(pkg);
@@ -3973,6 +3997,7 @@
throw new IllegalArgumentException("Cannot delete default channel");
}
enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
+ enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
callingUser, REASON_CHANNEL_REMOVED, null);
boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
@@ -4017,8 +4042,9 @@
final int userId = UserHandle.getUserId(callingUid);
List<NotificationChannel> groupChannels = groupToDelete.getChannels();
for (int i = 0; i < groupChannels.size(); i++) {
- enforceDeletingChannelHasNoFgService(pkg, userId,
- groupChannels.get(i).getId());
+ final String channelId = groupChannels.get(i).getId();
+ enforceDeletingChannelHasNoFgService(pkg, userId, channelId);
+ enforceDeletingChannelHasNoUserInitiatedJob(pkg, userId, channelId);
}
List<NotificationChannel> deletedChannels =
mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
@@ -6360,61 +6386,73 @@
checkCallerIsSystem();
mHandler.post(() -> {
synchronized (mNotificationLock) {
- int count = getNotificationCount(pkg, userId);
- boolean removeFgsNotification = false;
- if (count > MAX_PACKAGE_NOTIFICATIONS) {
- mUsageStats.registerOverCountQuota(pkg);
- removeFgsNotification = true;
- }
- if (removeFgsNotification) {
- NotificationRecord r = findNotificationLocked(pkg, null, notificationId,
- userId);
- if (r != null) {
- if (DBG) {
- Slog.d(TAG, "Remove FGS flag not allow. Cancel FGS notification");
- }
- removeFromNotificationListsLocked(r);
- cancelNotificationLocked(r, false, REASON_APP_CANCEL, true,
- null, SystemClock.elapsedRealtime());
- }
- } else {
- // strip flag from all enqueued notifications. listeners will be informed
- // in post runnable.
- List<NotificationRecord> enqueued = findNotificationsByListLocked(
- mEnqueuedNotifications, pkg, null, notificationId, userId);
- for (int i = 0; i < enqueued.size(); i++) {
- removeForegroundServiceFlagLocked(enqueued.get(i));
- }
-
- // if posted notification exists, strip its flag and tell listeners
- NotificationRecord r = findNotificationByListLocked(
- mNotificationList, pkg, null, notificationId, userId);
- if (r != null) {
- removeForegroundServiceFlagLocked(r);
- mRankingHelper.sort(mNotificationList);
- mListeners.notifyPostedLocked(r, r);
- }
- }
+ removeFlagFromNotificationLocked(pkg, notificationId, userId,
+ FLAG_FOREGROUND_SERVICE);
}
});
}
@Override
- public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
- onConversationRemovedInternal(pkg, uid, shortcuts);
+ public void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId,
+ int userId) {
+ checkCallerIsSystem();
+ mHandler.post(() -> {
+ synchronized (mNotificationLock) {
+ removeFlagFromNotificationLocked(pkg, notificationId, userId,
+ FLAG_USER_INITIATED_JOB);
+ }
+ });
}
@GuardedBy("mNotificationLock")
- private void removeForegroundServiceFlagLocked(NotificationRecord r) {
- if (r == null) {
- return;
+ private void removeFlagFromNotificationLocked(String pkg, int notificationId, int userId,
+ int flag) {
+ int count = getNotificationCount(pkg, userId);
+ boolean removeFlagFromNotification = false;
+ if (count > MAX_PACKAGE_NOTIFICATIONS) {
+ mUsageStats.registerOverCountQuota(pkg);
+ removeFlagFromNotification = true;
}
- StatusBarNotification sbn = r.getSbn();
- // NoMan adds flags FLAG_ONGOING_EVENT when it sees
- // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove
- // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received
- // initially *and* force remove FLAG_FOREGROUND_SERVICE.
- sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE);
+ if (removeFlagFromNotification) {
+ NotificationRecord r = findNotificationLocked(pkg, null, notificationId, userId);
+ if (r != null) {
+ if (DBG) {
+ final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
+ Slog.d(TAG, "Remove " + type + " flag not allow. "
+ + "Cancel " + type + " notification");
+ }
+ removeFromNotificationListsLocked(r);
+ cancelNotificationLocked(r, false, REASON_APP_CANCEL, true,
+ null, SystemClock.elapsedRealtime());
+ }
+ } else {
+ List<NotificationRecord> enqueued = findNotificationsByListLocked(
+ mEnqueuedNotifications, pkg, null, notificationId, userId);
+ for (int i = 0; i < enqueued.size(); i++) {
+ final NotificationRecord r = enqueued.get(i);
+ if (r != null) {
+ // strip flag from all enqueued notifications. listeners will be informed
+ // in post runnable.
+ StatusBarNotification sbn = r.getSbn();
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ }
+ }
+
+ NotificationRecord r = findNotificationByListLocked(
+ mNotificationList, pkg, null, notificationId, userId);
+ if (r != null) {
+ // if posted notification exists, strip its flag and tell listeners
+ StatusBarNotification sbn = r.getSbn();
+ sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+ mRankingHelper.sort(mNotificationList);
+ mListeners.notifyPostedLocked(r, r);
+ }
+ }
+ }
+
+ @Override
+ public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
+ onConversationRemovedInternal(pkg, uid, shortcuts);
}
@Override
@@ -6490,10 +6528,10 @@
}
}
- // Don't allow client applications to cancel foreground service notifs or autobundled
- // summaries.
+ // Don't allow client applications to cancel foreground service notifs, user-initiated job
+ // notifs or autobundled summaries.
final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
- (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
+ (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY);
cancelNotification(uid, callingPid, pkg, tag, id, 0,
mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
}
@@ -6547,9 +6585,16 @@
final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
notification, tag, id, pkg, userId);
+ boolean stripUijFlag = true;
+ final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+ if (js != null) {
+ stripUijFlag = !js.isNotificationAssociatedWithAnyUserInitiatedJobs(id, userId, pkg);
+ }
+
// Fix the notification as best we can.
try {
- fixNotification(notification, pkg, tag, id, userId, notificationUid, policy);
+ fixNotification(notification, pkg, tag, id, userId, notificationUid,
+ policy, stripUijFlag);
} catch (Exception e) {
if (notification.isForegroundService()) {
throw new SecurityException("Invalid FGS notification", e);
@@ -6558,7 +6603,6 @@
return;
}
-
if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
// Proceed if the notification is already showing/known, otherwise ignore
// because the service lifecycle logic has retained responsibility for its
@@ -6617,31 +6661,25 @@
boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
r.setImportanceFixed(isImportanceFixed);
- if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- final boolean fgServiceShown = channel.isFgServiceShown();
+ if (notification.isFgsOrUij()) {
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
- || !fgServiceShown)
+ || !channel.isUserVisibleTaskShown())
&& (r.getImportance() == IMPORTANCE_MIN
|| r.getImportance() == IMPORTANCE_NONE)) {
- // Increase the importance of foreground service notifications unless the user had
- // an opinion otherwise (and the channel hasn't yet shown a fg service).
- if (TextUtils.isEmpty(channelId)
- || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
- r.setSystemImportance(IMPORTANCE_LOW);
- } else {
- channel.setImportance(IMPORTANCE_LOW);
- r.setSystemImportance(IMPORTANCE_LOW);
- if (!fgServiceShown) {
- channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- channel.setFgServiceShown(true);
- }
- mPreferencesHelper.updateNotificationChannel(
- pkg, notificationUid, channel, false);
- r.updateNotificationChannel(channel);
+ // Increase the importance of fgs/uij notifications unless the user had
+ // an opinion otherwise (and the channel hasn't yet shown a fgs/uij).
+ channel.setImportance(IMPORTANCE_LOW);
+ r.setSystemImportance(IMPORTANCE_LOW);
+ if (!channel.isUserVisibleTaskShown()) {
+ channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ channel.setUserVisibleTaskShown(true);
}
- } else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
+ mPreferencesHelper.updateNotificationChannel(
+ pkg, notificationUid, channel, false);
+ r.updateNotificationChannel(channel);
+ } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
- channel.setFgServiceShown(true);
+ channel.setUserVisibleTaskShown(true);
r.updateNotificationChannel(channel);
}
}
@@ -6734,7 +6772,8 @@
@VisibleForTesting
protected void fixNotification(Notification notification, String pkg, String tag, int id,
- @UserIdInt int userId, int notificationUid, ServiceNotificationPolicy fgsPolicy)
+ @UserIdInt int userId, int notificationUid,
+ ServiceNotificationPolicy fgsPolicy, boolean stripUijFlag)
throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
@@ -6744,6 +6783,14 @@
if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
notification.flags &= ~FLAG_FOREGROUND_SERVICE;
}
+ if (notification.isUserInitiatedJob() && stripUijFlag) {
+ notification.flags &= ~FLAG_USER_INITIATED_JOB;
+ }
+
+ // Remove FLAG_AUTO_CANCEL from notifications that are associated with a FGS or UIJ.
+ if (notification.isFgsOrUij()) {
+ notification.flags &= ~FLAG_AUTO_CANCEL;
+ }
// Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
@@ -7088,8 +7135,8 @@
}
}
- // limit the number of non-fgs outstanding notificationrecords an app can have
- if (!n.isForegroundService()) {
+ // limit the number of non-fgs/uij outstanding notificationrecords an app can have
+ if (!n.isFgsOrUij()) {
int count = getNotificationCount(pkg, userId, id, tag);
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
mUsageStats.registerOverCountQuota(pkg);
@@ -7449,7 +7496,8 @@
return false;
}
} else if (mReason == REASON_APP_CANCEL) {
- if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
+ if ((flags & FLAG_FOREGROUND_SERVICE) != 0
+ || (flags & FLAG_USER_INITIATED_JOB) != 0) {
return false;
}
}
@@ -8002,7 +8050,7 @@
}
FlagChecker childrenFlagChecker = (flags) -> {
- if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
+ if ((flags & FLAG_FOREGROUND_SERVICE) != 0 || (flags & FLAG_USER_INITIATED_JOB) != 0) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 9e91875..ffe33a8 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -383,6 +383,7 @@
public int numWithBigText;
public int numWithBigPicture;
public int numForegroundService;
+ public int numUserInitiatedJob;
public int numOngoing;
public int numAutoCancel;
public int numWithLargeIcon;
@@ -433,6 +434,10 @@
numForegroundService++;
}
+ if ((n.flags & Notification.FLAG_USER_INITIATED_JOB) != 0) {
+ numUserInitiatedJob++;
+ }
+
if ((n.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
numOngoing++;
}
@@ -516,6 +521,7 @@
maybeCount("note_big_text", (numWithBigText - previous.numWithBigText));
maybeCount("note_big_pic", (numWithBigPicture - previous.numWithBigPicture));
maybeCount("note_fg", (numForegroundService - previous.numForegroundService));
+ maybeCount("note_uij", (numUserInitiatedJob - previous.numUserInitiatedJob));
maybeCount("note_ongoing", (numOngoing - previous.numOngoing));
maybeCount("note_auto", (numAutoCancel - previous.numAutoCancel));
maybeCount("note_large_icon", (numWithLargeIcon - previous.numWithLargeIcon));
@@ -550,6 +556,7 @@
previous.numWithBigText = numWithBigText;
previous.numWithBigPicture = numWithBigPicture;
previous.numForegroundService = numForegroundService;
+ previous.numUserInitiatedJob = numUserInitiatedJob;
previous.numOngoing = numOngoing;
previous.numAutoCancel = numAutoCancel;
previous.numWithLargeIcon = numWithLargeIcon;
@@ -645,6 +652,8 @@
output.append(indentPlusTwo);
output.append("numForegroundService=").append(numForegroundService).append("\n");
output.append(indentPlusTwo);
+ output.append("numUserInitiatedJob=").append(numUserInitiatedJob).append("\n");
+ output.append(indentPlusTwo);
output.append("numOngoing=").append(numOngoing).append("\n");
output.append(indentPlusTwo);
output.append("numAutoCancel=").append(numAutoCancel).append("\n");
@@ -701,6 +710,7 @@
maybePut(dump, "numWithBigText", numWithBigText);
maybePut(dump, "numWithBigPicture", numWithBigPicture);
maybePut(dump, "numForegroundService", numForegroundService);
+ maybePut(dump, "numUserInitiatedJob", numUserInitiatedJob);
maybePut(dump, "numOngoing", numOngoing);
maybePut(dump, "numAutoCancel", numAutoCancel);
maybePut(dump, "numWithLargeIcon", numWithLargeIcon);
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index cae7079..82b585d 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -65,7 +65,7 @@
private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
- Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+ Pattern.compile(".*\\bavc: +granted +\\{ execute(?:_no_trans|) \\} .*"
+ "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+ "\\bscontext=u:r:untrusted_app(?:_25|_27)?:.*"
+ "\\btcontext=u:object_r:app_data_file:.*"
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 84bee50..402fb30 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
@@ -25,6 +26,8 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.content.PermissionChecker.checkCallingOrSelfPermissionForPreflight;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
@@ -32,6 +35,7 @@
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -90,6 +94,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.window.IDumpCallback;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -104,6 +109,15 @@
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.wm.ActivityTaskManagerInternal;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -111,6 +125,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
@@ -118,6 +133,15 @@
* managed profiles.
*/
public class LauncherAppsService extends SystemService {
+ private static final String WM_TRACE_DIR = "/data/misc/wmtrace/";
+ private static final String VC_FILE_SUFFIX = ".vc";
+
+ private static final Set<PosixFilePermission> WM_TRACE_FILE_PERMISSIONS = Set.of(
+ PosixFilePermission.OWNER_WRITE,
+ PosixFilePermission.GROUP_READ,
+ PosixFilePermission.OTHERS_READ,
+ PosixFilePermission.OWNER_READ
+ );
private final LauncherAppsImpl mLauncherAppsImpl;
@@ -191,6 +215,8 @@
final LauncherAppsServiceInternal mInternal;
+ private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
+
public LauncherAppsImpl(Context context) {
mContext = context;
mIPM = AppGlobals.getPackageManager();
@@ -1431,6 +1457,66 @@
getActivityOptionsForLauncher(opts), user.getIdentifier());
}
+
+ /**
+ * Using a pipe, outputs view capture data to the wmtrace dir
+ */
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+
+ // Before the wmtrace directory is picked up by dumpstate service, some processes need
+ // to write their data to that location. They can do that via these dumpCallbacks.
+ int i = mDumpCallbacks.beginBroadcast();
+ while (i > 0) {
+ i--;
+ dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i,
+ mDumpCallbacks.getBroadcastItem(i));
+ }
+ mDumpCallbacks.finishBroadcast();
+ }
+
+ private void dumpDataToWmTrace(String name, IDumpCallback cb) {
+ ParcelFileDescriptor[] pipe;
+ try {
+ pipe = ParcelFileDescriptor.createPipe();
+ cb.onDump(pipe[1]);
+ } catch (IOException | RemoteException e) {
+ Log.d(TAG, "failed to pipe view capture data", e);
+ return;
+ }
+
+ Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName());
+ try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {
+ Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
+ Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS);
+ } catch (IOException e) {
+ Log.d(TAG, "failed to write data to file in wmtrace dir", e);
+ }
+ }
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
+ public void registerDumpCallback(IDumpCallback cb) {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+ mDumpCallbacks.register(cb, name);
+ } else {
+ Log.w(TAG, "caller lacks permissions to registerDumpCallback");
+ }
+ }
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
+ public void unRegisterDumpCallback(IDumpCallback cb) {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ mDumpCallbacks.unregister(cb);
+ } else {
+ Log.w(TAG, "caller lacks permissions to unRegisterDumpCallback");
+ }
+ }
+
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 69e92e0..f358ce7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -677,7 +677,8 @@
? params.installerPackageName : installerPackageName;
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
- || PackageInstallerSession.isSystemDataLoaderInstallation(params)) {
+ || PackageInstallerSession.isSystemDataLoaderInstallation(params)
+ || PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not the
// initiatingPackageName
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ea6383e..006d7c8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -745,6 +745,9 @@
@GuardedBy("mLock")
private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION;
+ @GuardedBy("mLock")
+ private boolean mAllowsUpdateOwnership = true;
+
private static final FileFilter sAddedApkFilter = new FileFilter() {
@Override
public boolean accept(File file) {
@@ -866,13 +869,11 @@
private static final int USER_ACTION_NOT_NEEDED = 0;
private static final int USER_ACTION_REQUIRED = 1;
- private static final int USER_ACTION_PENDING_APK_PARSING = 2;
private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER = 3;
@IntDef({
USER_ACTION_NOT_NEEDED,
USER_ACTION_REQUIRED,
- USER_ACTION_PENDING_APK_PARSING,
USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER,
})
@interface UserActionRequirement {}
@@ -963,11 +964,11 @@
&& !isApexSession()
&& !isUpdateOwner
&& !isInstallerShell
+ && mAllowsUpdateOwnership
// We don't enforce the update ownership for the managed user and profile.
&& !isFromManagedUserOrProfile) {
return USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER;
}
-
if (isPermissionGranted) {
return USER_ACTION_NOT_NEEDED;
}
@@ -982,7 +983,20 @@
&& isUpdateWithoutUserActionPermissionGranted
&& ((isUpdateOwnershipEnforcementEnabled ? isUpdateOwner
: isInstallerOfRecord) || isSelfUpdate)) {
- return USER_ACTION_PENDING_APK_PARSING;
+ if (!isApexSession()) {
+ if (!isTargetSdkConditionSatisfied(this)) {
+ return USER_ACTION_REQUIRED;
+ }
+
+ if (!mSilentUpdatePolicy.isSilentUpdateAllowed(
+ getInstallerPackageName(), getPackageName())) {
+ // Fall back to the non-silent update if a repeated installation is invoked
+ // within the throttle time.
+ return USER_ACTION_REQUIRED;
+ }
+ mSilentUpdatePolicy.track(getInstallerPackageName(), getPackageName());
+ return USER_ACTION_NOT_NEEDED;
+ }
}
return USER_ACTION_REQUIRED;
@@ -1442,7 +1456,10 @@
@NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
assertCallerIsOwnerRootOrVerifier();
final File file = new File(stageDir, name);
- final String installerPackageName = getInstallSource().mInitiatingPackageName;
+ final String installerPackageName = PackageManagerServiceUtils.isInstalledByAdb(
+ getInstallSource().mInitiatingPackageName)
+ ? getInstallSource().mInstallerPackageName
+ : getInstallSource().mInitiatingPackageName;
try {
mPm.requestFileChecksums(file, installerPackageName, optional, required,
trustedInstallers, onChecksumsReadyListener);
@@ -2363,26 +2380,6 @@
session.sendPendingUserActionIntent(target);
return true;
}
-
- if (!session.isApexSession() && userActionRequirement == USER_ACTION_PENDING_APK_PARSING) {
- if (!isTargetSdkConditionSatisfied(session)) {
- session.sendPendingUserActionIntent(target);
- return true;
- }
-
- if (session.params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) {
- if (!session.mSilentUpdatePolicy.isSilentUpdateAllowed(
- session.getInstallerPackageName(), session.getPackageName())) {
- // Fall back to the non-silent update if a repeated installation is invoked
- // within the throttle time.
- session.sendPendingUserActionIntent(target);
- return true;
- }
- session.mSilentUpdatePolicy.track(session.getInstallerPackageName(),
- session.getPackageName());
- }
- }
-
return false;
}
@@ -3393,6 +3390,8 @@
// {@link PackageLite#getTargetSdk()}
mValidatedTargetSdk = packageLite.getTargetSdk();
+ mAllowsUpdateOwnership = packageLite.isAllowUpdateOwnership();
+
return packageLite;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6b213b7..e4e3a9d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7273,7 +7273,7 @@
final long token = Binder.clearCallingIdentity();
try {
return DeviceConfig.getBoolean(NAMESPACE_PACKAGE_MANAGER_SERVICE,
- PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, false /* defaultValue */);
+ PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, true /* defaultValue */);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 42538f3..db997d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -36,6 +36,7 @@
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1389,6 +1390,14 @@
}
/**
+ * Check if a UID is non-system UID adopted shell permission.
+ */
+ public static boolean isAdoptedShell(int uid, Context context) {
+ return uid != Process.SYSTEM_UID && context.checkCallingOrSelfPermission(
+ Manifest.permission.USE_SYSTEM_DATA_LOADERS) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
* Check if a UID is system UID or shell's UID.
*/
public static boolean isRootOrShell(int uid) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cc60802..89f46fe 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -692,7 +692,7 @@
null /* usesSplitNames */, null /* configForSplit */,
null /* splitApkPaths */, null /* splitRevisionCodes */,
apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
- null /* splitTypes */);
+ null /* splitTypes */, apkLite.isAllowUpdateOwnership());
sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
params.sessionParams.abiOverride, fd.getFileDescriptor());
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 50f88d3..b669ba2 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -27,6 +27,7 @@
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.IUriGrantsManager;
+import android.app.UidObserver;
import android.app.UriGrantsManager;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -590,7 +591,7 @@
}
}
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ final private IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
injectPostToHandler(() -> handleOnUidStateChanged(uid, procState));
@@ -601,20 +602,6 @@
injectPostToHandler(() ->
handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT));
}
-
- @Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override public void onUidProcAdjChanged(int uid) {
- }
};
void handleOnUidStateChanged(int uid, int procState) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b7a2b86..a814ca4 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -136,6 +136,7 @@
com.android.internal.R.color.system_neutral2_900)
.setDefaultRestrictions(null)
.setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
+ .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
@@ -216,7 +217,8 @@
com.android.internal.R.color.profile_badge_1_dark,
com.android.internal.R.color.profile_badge_2_dark,
com.android.internal.R.color.profile_badge_3_dark)
- .setDefaultRestrictions(restrictions);
+ .setDefaultRestrictions(restrictions)
+ .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings());
}
/**
@@ -337,6 +339,15 @@
return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
}
+ /** Gets a default bundle, keyed by Settings.Secure String names, for non-managed profiles. */
+ private static Bundle getDefaultNonManagedProfileSecureSettings() {
+ final Bundle settings = new Bundle();
+ // Non-managed profiles go through neither SetupWizard nor DPC flows, so we automatically
+ // mark them as setup.
+ settings.putString(android.provider.Settings.Secure.USER_SETUP_COMPLETE, "1");
+ return settings;
+ }
+
/**
* Reads the given xml parser to obtain device user-type customization, and updates the given
* map of {@link UserTypeDetails.Builder}s accordingly.
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 8641b41..3296c1f 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -22,6 +22,7 @@
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IUidObserver;
+import android.app.UidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -168,7 +169,7 @@
private final Object mInnerLock = new Object();
private final Object mToken = new Object();
- private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
+ private final IUidObserver mObserver = new UidObserver() {
@Override
public void onUidGone(int uid, boolean disabled) {
if (uid == mUid) {
@@ -188,15 +189,6 @@
}
}
}
-
- public void onUidActive(int uid) {
- }
- public void onUidIdle(int uid, boolean disabled) {
- }
- public void onUidProcAdjChanged(int uid) {
- }
- public void onUidCachedChanged(int uid, boolean cached) {
- }
};
private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 1a22b89..1a91d25 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -19,8 +19,8 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.IUidObserver;
import android.app.StatsManager;
+import android.app.UidObserver;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -69,7 +69,7 @@
/** Lock to protect HAL handles and listen list. */
private final Object mLock = new Object();
- @VisibleForTesting final UidObserver mUidObserver;
+ @VisibleForTesting final MyUidObserver mUidObserver;
private final NativeWrapper mNativeWrapper;
@@ -94,7 +94,7 @@
mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
- mUidObserver = new UidObserver();
+ mUidObserver = new MyUidObserver();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
}
@@ -246,7 +246,7 @@
}
@VisibleForTesting
- final class UidObserver extends IUidObserver.Stub {
+ final class MyUidObserver extends UidObserver {
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
@@ -276,14 +276,6 @@
});
}
- @Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
/**
* The IUidObserver callback is called from the system_server, so it'll be a direct function
* call from ActivityManagerService. Do not do heavy logic here.
@@ -305,14 +297,6 @@
}
});
}
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override
- public void onUidProcAdjChanged(int uid) {
- }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
index 2d3ede0..f586126 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -70,12 +70,14 @@
Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
wrapCallback(mCallback::onCancel);
} else if (e instanceof RkpProxyException) {
- Log.e(TAG, "RKP error fetching key for client " + mCallback.hashCode(), e);
+ Log.e(TAG, "RKP error fetching key for client " + mCallback.hashCode() + ": "
+ + e.getMessage());
RkpProxyException rkpException = (RkpProxyException) e;
wrapCallback(() -> mCallback.onError(toGetKeyError(rkpException),
e.getMessage()));
} else {
- Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e);
+ Log.e(TAG, "Unknown error fetching key for client " + mCallback.hashCode() + ": "
+ + e.getMessage());
wrapCallback(() -> mCallback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN,
e.getMessage()));
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8a7d607c..1ab7f362 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -30,7 +30,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.IUidObserver;
+import android.app.UidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -157,7 +157,7 @@
@VisibleForTesting
final SettingsContentObserver mSettingObserver;
@VisibleForTesting
- final UidObserver mUidObserver;
+ final MyUidObserver mUidObserver;
@VisibleForTesting
final SettingsBroadcastReceiver mSettingChangeReceiver;
final VirtualDeviceListener mVirtualDeviceListener;
@@ -195,7 +195,7 @@
mContext = context;
mVibrationConfig = config;
mSettingObserver = new SettingsContentObserver(handler);
- mUidObserver = new UidObserver();
+ mUidObserver = new MyUidObserver();
mSettingChangeReceiver = new SettingsBroadcastReceiver();
mVirtualDeviceListener = new VirtualDeviceListener();
@@ -726,7 +726,7 @@
/** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
@VisibleForTesting
- final class UidObserver extends IUidObserver.Stub {
+ final class MyUidObserver extends UidObserver {
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
@@ -740,25 +740,9 @@
}
@Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
mProcStatesCache.put(uid, procState);
}
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override
- public void onUidProcAdjChanged(int uid) {
- }
}
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 799d1494..53861c8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -29,6 +29,7 @@
import android.annotation.Nullable;
import android.app.WallpaperColors;
+import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.backup.WallpaperBackupHelper;
import android.content.ComponentName;
@@ -75,17 +76,17 @@
private final WallpaperCropper mWallpaperCropper;
private final Context mContext;
- // Temporary feature flag. TODO(b/197814683) remove
- private final boolean mEnableSeparateLockScreenEngine;
+ private final boolean mIsLockscreenLiveWallpaperEnabled;
WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
- WallpaperCropper wallpaperCropper, boolean enableSeparateLockScreenEngine) {
+ WallpaperCropper wallpaperCropper) {
mContext = context;
mWallpaperDisplayHelper = wallpaperDisplayHelper;
mWallpaperCropper = wallpaperCropper;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
- mEnableSeparateLockScreenEngine = enableSeparateLockScreenEngine;
+ mIsLockscreenLiveWallpaperEnabled = context.getSystemService(WallpaperManager.class)
+ .isLockscreenLiveWallpaperEnabled();
}
private JournaledFile makeJournaledFile(int userId) {
@@ -135,8 +136,9 @@
* If null, a new object will be created.
* @param lockWallpaper the lock wallpaper object to reuse to do the modifications.
* If null, a new object will be created.
- * @param which The wallpaper(s) to load. If {@link #mEnableSeparateLockScreenEngine} is false,
- * this flag has no effect and both wallpapers will always be loaded.
+ * @param which The wallpaper(s) to load. Only has effect if
+ * {@link WallpaperManager#isLockscreenLiveWallpaperEnabled} is true,
+ * otherwise both wallpaper will always be loaded.
* @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
* This object will contain the {@code wallpaper} and
* {@code lockWallpaper} provided as parameters, if they are not null.
@@ -148,11 +150,13 @@
File file = journal.chooseForRead();
boolean migrateFromOld = wallpaper == null;
- boolean loadSystem = !mEnableSeparateLockScreenEngine || (which & FLAG_SYSTEM) != 0;
- boolean loadLock = !mEnableSeparateLockScreenEngine || (which & FLAG_LOCK) != 0;
+
+ boolean separateLockscreenEngine = mIsLockscreenLiveWallpaperEnabled;
+ boolean loadSystem = !separateLockscreenEngine || (which & FLAG_SYSTEM) != 0;
+ boolean loadLock = !separateLockscreenEngine || (which & FLAG_LOCK) != 0;
// don't reuse the wallpaper objects in the new version
- if (mEnableSeparateLockScreenEngine) {
+ if (separateLockscreenEngine) {
wallpaper = null;
lockWallpaper = null;
}
@@ -184,7 +188,8 @@
if (type == XmlPullParser.START_TAG) {
String tag = parser.getName();
if (("wp".equals(tag) && loadSystem)
- || ("kwp".equals(tag) && mEnableSeparateLockScreenEngine && loadLock)) {
+ || ("kwp".equals(tag) && mIsLockscreenLiveWallpaperEnabled
+ && loadLock)) {
if ("kwp".equals(tag) && lockWallpaper == null) {
lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
@@ -213,7 +218,7 @@
Slog.v(TAG, "mNextWallpaperComponent:"
+ wallpaper.nextWallpaperComponent);
}
- } else if ("kwp".equals(tag) && !mEnableSeparateLockScreenEngine) {
+ } else if ("kwp".equals(tag) && !mIsLockscreenLiveWallpaperEnabled) {
// keyguard-specific wallpaper for this user (legacy code)
if (lockWallpaper == null) {
lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 93f039d..b1b0c55 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -88,6 +88,7 @@
import android.os.SELinux;
import android.os.ShellCallback;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -179,8 +180,7 @@
private final Object mLock = new Object();
/** True to enable a second engine for lock screen wallpaper when different from system wp. */
- @VisibleForTesting
- final boolean mEnableSeparateLockScreenEngine;
+ private final boolean mIsLockscreenLiveWallpaperEnabled;
/** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
@@ -230,7 +230,7 @@
}
// Handles static wallpaper changes generated by WallpaperObserver events when
- // mEnableSeparateLockScreenEngine is true.
+ // enableSeparateLockScreenEngine() is true.
private void updateWallpapers(int event, String path) {
// System and system+lock changes happen on the system wallpaper input file;
// lock-only changes happen on the dedicated lock wallpaper input file
@@ -381,7 +381,7 @@
}
// Handles static wallpaper changes generated by WallpaperObserver events when
- // mEnableSeparateLockScreenEngine is false.
+ // enableSeparateLockScreenEngine() is false.
// TODO(b/266818039) Remove this method
private void updateWallpapersLegacy(int event, String path) {
final boolean moved = (event == MOVED_TO);
@@ -498,7 +498,7 @@
return;
}
- if (mEnableSeparateLockScreenEngine) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
updateWallpapers(event, path);
} else {
updateWallpapersLegacy(event, path);
@@ -1600,15 +1600,11 @@
mActivityManager = mContext.getSystemService(ActivityManager.class);
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
-
- mEnableSeparateLockScreenEngine = mContext.getResources().getBoolean(
- R.bool.config_independentLockscreenLiveWallpaper);
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
- mWallpaperCropper, mEnableSeparateLockScreenEngine);
- if (DEBUG) {
- Slog.v(TAG, "Separate lock screen engine enabled: " + mEnableSeparateLockScreenEngine);
- }
+ mWallpaperCropper);
+ mIsLockscreenLiveWallpaperEnabled =
+ SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
}
@@ -2794,7 +2790,7 @@
Os.rename(sysWP.wallpaperFile.getAbsolutePath(), lockWP.wallpaperFile.getAbsolutePath());
Os.rename(sysWP.cropFile.getAbsolutePath(), lockWP.cropFile.getAbsolutePath());
mLockWallpaperMap.put(userId, lockWP);
- if (mEnableSeparateLockScreenEngine) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
SELinux.restorecon(lockWP.wallpaperFile);
mLastLockWallpaper = lockWP;
}
@@ -2858,7 +2854,7 @@
@VisibleForTesting
void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which, int userId) {
- if (mEnableSeparateLockScreenEngine) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
setWallpaperComponentInternal(name, which, userId);
} else {
setWallpaperComponentInternalLegacy(name, which, userId);
@@ -3192,7 +3188,7 @@
Slog.w(TAG, msg);
return false;
}
- if (mEnableSeparateLockScreenEngine) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
maybeDetachLastWallpapers(wallpaper);
} else if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null
&& !wallpaper.equals(mFallbackWallpaper)) {
@@ -3201,7 +3197,7 @@
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
newConn.mReply = reply;
- if (mEnableSeparateLockScreenEngine) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
updateCurrentWallpapers(wallpaper);
} else if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(
mFallbackWallpaper)) {
@@ -3221,8 +3217,8 @@
return true;
}
- // Updates tracking of the currently bound wallpapers. Assumes mEnableSeparateLockScreenEngine
- // is true.
+ // Updates tracking of the currently bound wallpapers.
+ // Assumes isLockscreenLiveWallpaperEnabled is true.
private void updateCurrentWallpapers(WallpaperData newWallpaper) {
if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
return;
@@ -3237,7 +3233,7 @@
}
// Detaches previously bound wallpapers if no longer in use. Assumes
- // mEnableSeparateLockScreenEngine is true.
+ // isLockscreenLiveWallpaperEnabled is true.
private void maybeDetachLastWallpapers(WallpaperData newWallpaper) {
if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
return;
@@ -3389,6 +3385,11 @@
return (wallpaper != null) ? wallpaper.allowBackup : false;
}
+ @Override
+ public boolean isLockscreenLiveWallpaperEnabled() {
+ return mIsLockscreenLiveWallpaperEnabled;
+ }
+
private void onDisplayReadyInternal(int displayId) {
synchronized (mLock) {
if (mLastWallpaper == null) {
@@ -3476,8 +3477,8 @@
WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked(
userId, keepDimensionHints, wallpaperData, lockWallpaperData, which);
- boolean updateSystem = !mEnableSeparateLockScreenEngine || (which & FLAG_SYSTEM) != 0;
- boolean updateLock = !mEnableSeparateLockScreenEngine || (which & FLAG_LOCK) != 0;
+ boolean updateSystem = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_SYSTEM) != 0;
+ boolean updateLock = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_LOCK) != 0;
if (updateSystem) mWallpaperMap.put(userId, result.getSystemWallpaperData());
if (updateLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5f56923..8346e7c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4068,7 +4068,7 @@
}
void finishRelaunching() {
- mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false);
+ mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -9500,7 +9500,7 @@
mRelaunchReason = RELAUNCH_REASON_NONE;
}
if (isRequestedOrientationChanged) {
- mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true);
+ mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
}
if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 587e720..be80b01 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -585,14 +585,14 @@
* The closing target should only exist in close list, but the opening target can be either in
* open or close list.
*/
- void onTransactionReady(Transition transition) {
+ void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
if (!isMonitoringTransition()) {
return;
}
- final ArraySet<WindowContainer> targets = transition.mParticipants;
for (int i = targets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = targets.valueAt(i);
- if (wc.asActivityRecord() == null && wc.asTask() == null) {
+ final WindowContainer wc = targets.get(i).mContainer;
+ if (wc.asActivityRecord() == null && wc.asTask() == null
+ && wc.asTaskFragment() == null) {
continue;
}
// WC can be visible due to setLaunchBehind
@@ -605,6 +605,9 @@
final boolean matchAnimationTargets = isWaitBackTransition()
&& (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
&& mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+ mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
if (!matchAnimationTargets) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
} else {
@@ -829,10 +832,16 @@
if (!mComposed) {
return false;
}
+
+ // WC must be ActivityRecord in legacy transition, but it also can be Task or
+ // TaskFragment when using Shell transition.
+ // Open target: Can be Task or ActivityRecord or TaskFragment
+ // Close target: Limit to the top activity for now, to reduce the chance of misjudgment.
final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
if (mSwitchType == TASK_SWITCH) {
return wc == target
- || (wc.asTask() != null && wc.hasChild(target));
+ || (wc.asTask() != null && wc.hasChild(target))
+ || (wc.asActivityRecord() != null && target.hasChild(wc));
} else if (mSwitchType == ACTIVITY_SWITCH) {
return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 6773bcd..e447049 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -487,7 +487,7 @@
// The verdict changed from allow (resultIfPiSenderAllowsBal) to block, PI sender
// default change is on (otherwise we would have fallen into if above) and we'd
// allow if it were off
- Slog.wtf(TAG, "Without BAL hardening this activity start would NOT be allowed!"
+ Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed!"
+ stateDumpLog);
}
}
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 002c32e..e88cfbf 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -254,11 +254,12 @@
*
* If {@code entity} is already added, this method will update its {@code originatingToken}.
*/
- void addOrUpdateAllowBackgroundStartPrivileges(
- Binder entity, BackgroundStartPrivileges backgroundStartPrivileges) {
+ void addOrUpdateAllowBackgroundStartPrivileges(@NonNull Binder entity,
+ @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
requireNonNull(entity, "entity");
requireNonNull(backgroundStartPrivileges, "backgroundStartPrivileges");
- checkArgument(backgroundStartPrivileges.allowsAny());
+ checkArgument(backgroundStartPrivileges.allowsAny(),
+ "backgroundStartPrivileges does not allow anything");
synchronized (this) {
if (mBackgroundStartPrivileges == null) {
mBackgroundStartPrivileges = new ArrayMap<>();
@@ -271,7 +272,7 @@
* Removes token {@code entity} that allowed background activity starts added via {@link
* #addOrUpdateAllowBackgroundStartPrivileges(Binder, BackgroundStartPrivileges)}.
*/
- void removeAllowBackgroundStartPrivileges(Binder entity) {
+ void removeAllowBackgroundStartPrivileges(@NonNull Binder entity) {
requireNonNull(entity, "entity");
synchronized (this) {
if (mBackgroundStartPrivileges != null) {
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index f1c5f911..b808a55 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -296,6 +296,9 @@
+ "state %d",
mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state);
+ // TODO(b/274790702): Do not start recording if waiting for consent - for now,
+ // go ahead.
+
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
mRecordedSurface = SurfaceControl.mirrorSurface(
mRecordedWindowContainer.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 13a1cb6..c6db8a7 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -126,6 +126,9 @@
boolean isVisible;
SurfaceAnimator mSurfaceAnimator;
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
/**
* Determines whether the dim layer should animate before destroying.
*/
@@ -260,11 +263,16 @@
* {@link WindowContainer#prepareSurfaces}. After calling this, the container should
* chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
* a chance to request dims to continue.
+ * @return Non-null dim bounds if the dimmer is showing.
*/
- void resetDimStates() {
- if (mDimState != null && !mDimState.mDontReset) {
+ Rect resetDimStates() {
+ if (mDimState == null) {
+ return null;
+ }
+ if (!mDimState.mDontReset) {
mDimState.mDimming = false;
}
+ return mDimState.mDimBounds;
}
void dontAnimateExit() {
@@ -275,13 +283,13 @@
/**
* Call after invoking {@link WindowContainer#prepareSurfaces} on children as
- * described in {@link #resetDimStates}.
+ * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates}
+ * should be set before calling this method.
*
* @param t A transaction in which to update the dims.
- * @param bounds The bounds at which to dim.
* @return true if any Dims were updated.
*/
- boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+ boolean updateDims(SurfaceControl.Transaction t) {
if (mDimState == null) {
return false;
}
@@ -297,6 +305,7 @@
mDimState = null;
return false;
} else {
+ final Rect bounds = mDimState.mDimBounds;
// TODO: Once we use geometry from hierarchy this falls away.
t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index c2ddb41..26f56a2 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -148,7 +148,7 @@
@ScreenOrientation
int getOrientation(int candidate) {
final int orientation = super.getOrientation(candidate);
- if (getIgnoreOrientationRequest(orientation)) {
+ if (shouldIgnoreOrientationRequest(orientation)) {
// In all the other case, mLastOrientationSource will be reassigned to a new value
mLastOrientationSource = null;
return SCREEN_ORIENTATION_UNSET;
@@ -158,7 +158,7 @@
@Override
boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) {
- return !getIgnoreOrientationRequest(orientation)
+ return !shouldIgnoreOrientationRequest(orientation)
&& super.handlesOrientationChangeFromDescendant(orientation);
}
@@ -169,7 +169,7 @@
final int orientation = requestingContainer != null
? requestingContainer.getOverrideOrientation()
: SCREEN_ORIENTATION_UNSET;
- return !getIgnoreOrientationRequest(orientation)
+ return !shouldIgnoreOrientationRequest(orientation)
&& super.onDescendantOrientationChanged(requestingContainer);
}
@@ -236,8 +236,7 @@
/**
* @return {@value true} if we need to ignore the orientation in input.
*/
- // TODO(b/262366204): Rename getIgnoreOrientationRequest to shouldIgnoreOrientationRequest
- boolean getIgnoreOrientationRequest(@ScreenOrientation int orientation) {
+ boolean shouldIgnoreOrientationRequest(@ScreenOrientation int orientation) {
// We always respect orientation request for ActivityInfo.SCREEN_ORIENTATION_LOCKED
// ActivityInfo.SCREEN_ORIENTATION_NOSENSOR.
// Main use case why this is important is Camera apps that rely on those
@@ -768,7 +767,6 @@
*/
static class Dimmable extends DisplayArea<DisplayArea> {
private final Dimmer mDimmer = new Dimmer(this);
- private final Rect mTmpDimBoundsRect = new Rect();
Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
super(wms, type, name, featureId);
@@ -781,11 +779,13 @@
@Override
void prepareSurfaces() {
- mDimmer.resetDimStates();
+ final Rect dimBounds = mDimmer.resetDimStates();
super.prepareSurfaces();
- // Bounds need to be relative, as the dim layer is a child.
- getBounds(mTmpDimBoundsRect);
- mTmpDimBoundsRect.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ if (dimBounds != null) {
+ // Bounds need to be relative, as the dim layer is a child.
+ getBounds(dimBounds);
+ dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ }
// If SystemUI is dragging for recents, we want to reset the dim state so any dim layer
// on the display level fades out.
@@ -793,8 +793,10 @@
mDimmer.resetDimStates();
}
- if (mDimmer.updateDims(getSyncTransaction(), mTmpDimBoundsRect)) {
- scheduleAnimation();
+ if (dimBounds != null) {
+ if (mDimmer.updateDims(getSyncTransaction())) {
+ scheduleAnimation();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 89cb13a..bec58b8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1648,7 +1648,7 @@
@Override
boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) {
- return !getIgnoreOrientationRequest(orientation)
+ return !shouldIgnoreOrientationRequest(orientation)
&& !getDisplayRotation().isFixedToUserRotation();
}
@@ -1752,7 +1752,7 @@
}
final int activityOrientation = r.getOverrideOrientation();
if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
- || getIgnoreOrientationRequest(activityOrientation)) {
+ || shouldIgnoreOrientationRequest(activityOrientation)) {
return ROTATION_UNDEFINED;
}
if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
@@ -2921,6 +2921,7 @@
/* includeRotationSettings */ false);
mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId,
mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
+ mDisplayRotation.physicalDisplayChanged();
}
// If there is an override set for base values - use it, otherwise use new values.
@@ -3291,7 +3292,6 @@
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
- mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
@@ -3305,6 +3305,7 @@
mWindowingLayer.release();
mInputMonitor.onDisplayRemoved();
mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
+ mDisplayRotation.onDisplayRemoved();
mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
mRootWindowContainer.mTaskSupervisor
.getKeyguardController().onDisplayRemoved(mDisplayId);
@@ -5148,7 +5149,7 @@
@ScreenOrientation
int getOrientation(@ScreenOrientation int candidate) {
// IME does not participate in orientation.
- return getIgnoreOrientationRequest(candidate) ? SCREEN_ORIENTATION_UNSET : candidate;
+ return shouldIgnoreOrientationRequest(candidate) ? SCREEN_ORIENTATION_UNSET : candidate;
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6af1c7c9..628f4d3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -51,8 +51,13 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.power.Boost;
import android.os.Handler;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
@@ -1085,6 +1090,10 @@
return false;
}
+ if (mFoldController != null && mFoldController.shouldDisableRotationSensor()) {
+ return false;
+ }
+
if (mSupportAutoRotation) {
if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
|| mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
@@ -1183,6 +1192,9 @@
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
+ if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
+ sensorRotation = -1;
+ }
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
@@ -1425,6 +1437,11 @@
return false;
}
+ // Do not show rotation choice when fold controller blocks rotation sensor
+ if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
+ return false;
+ }
+
// Don't show rotation choice if we are in tabletop or book modes.
if (isTabletopAutoRotateOverrideEnabled()) return false;
@@ -1527,6 +1544,13 @@
}
}
+ void onDisplayRemoved() {
+ removeDefaultDisplayRotationChangedCallback();
+ if (mFoldController != null) {
+ mFoldController.onDisplayRemoved();
+ }
+ }
+
/** Return whether the rotation settings has changed. */
private boolean updateSettings() {
final ContentResolver resolver = mContext.getContentResolver();
@@ -1622,6 +1646,22 @@
pw.println(prefix + " mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
pw.println(prefix + " mFixedToUserRotation=" + isFixedToUserRotation());
+ if (mFoldController != null) {
+ pw.println(prefix + "FoldController");
+ pw.println(prefix + " mPauseAutorotationDuringUnfolding="
+ + mFoldController.mPauseAutorotationDuringUnfolding);
+ pw.println(prefix + " mShouldDisableRotationSensor="
+ + mFoldController.mShouldDisableRotationSensor);
+ pw.println(prefix + " mShouldIgnoreSensorRotation="
+ + mFoldController.mShouldIgnoreSensorRotation);
+ pw.println(prefix + " mLastDisplaySwitchTime="
+ + mFoldController.mLastDisplaySwitchTime);
+ pw.println(prefix + " mLastHingeAngleEventTime="
+ + mFoldController.mLastHingeAngleEventTime);
+ pw.println(prefix + " mDeviceState="
+ + mFoldController.mDeviceState);
+ }
+
if (!mRotationHistory.mRecords.isEmpty()) {
pw.println();
pw.println(prefix + " RotationHistory");
@@ -1663,13 +1703,37 @@
}
}
- private class FoldController {
+ /**
+ * Called by the DisplayContent when the physical display changes
+ */
+ void physicalDisplayChanged() {
+ if (mFoldController != null) {
+ mFoldController.onPhysicalDisplayChanged();
+ }
+ }
+
+ @VisibleForTesting
+ long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ class FoldController {
+ private final boolean mPauseAutorotationDuringUnfolding;
@Surface.Rotation
private int mHalfFoldSavedRotation = -1; // No saved rotation
private DeviceStateController.DeviceState mDeviceState =
DeviceStateController.DeviceState.UNKNOWN;
+ private long mLastHingeAngleEventTime = 0;
+ private long mLastDisplaySwitchTime = 0;
+ private boolean mShouldIgnoreSensorRotation;
+ private boolean mShouldDisableRotationSensor;
private boolean mInHalfFoldTransition = false;
+ private int mDisplaySwitchRotationBlockTimeMs;
+ private int mHingeAngleRotationBlockTimeMs;
+ private int mMaxHingeAngle;
private final boolean mIsDisplayAlwaysSeparatingHinge;
+ private SensorManager mSensorManager;
+ private SensorEventListener mHingeAngleSensorEventListener;
private final Set<Integer> mTabletopRotations;
private final Runnable mActivityBoundsUpdateCallback;
@@ -1726,6 +1790,48 @@
}
}
};
+
+ mPauseAutorotationDuringUnfolding = mContext.getResources().getBoolean(
+ R.bool.config_windowManagerPauseRotationWhenUnfolding);
+
+ if (mPauseAutorotationDuringUnfolding) {
+ mDisplaySwitchRotationBlockTimeMs = mContext.getResources().getInteger(
+ R.integer.config_pauseRotationWhenUnfolding_displaySwitchTimeout);
+ mHingeAngleRotationBlockTimeMs = mContext.getResources().getInteger(
+ R.integer.config_pauseRotationWhenUnfolding_hingeEventTimeout);
+ mMaxHingeAngle = mContext.getResources().getInteger(
+ R.integer.config_pauseRotationWhenUnfolding_maxHingeAngle);
+ registerSensorManager();
+ }
+ }
+
+ private void registerSensorManager() {
+ mSensorManager = mContext.getSystemService(SensorManager.class);
+ if (mSensorManager != null) {
+ final Sensor hingeAngleSensor = mSensorManager
+ .getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
+
+ if (hingeAngleSensor != null) {
+ mHingeAngleSensorEventListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ onHingeAngleChanged(event.values[0]);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+ mSensorManager.registerListener(mHingeAngleSensorEventListener,
+ hingeAngleSensor, SensorManager.SENSOR_DELAY_FASTEST, getHandler());
+ }
+ }
+ }
+
+ void onDisplayRemoved() {
+ if (mSensorManager != null && mHingeAngleSensorEventListener != null) {
+ mSensorManager.unregisterListener(mHingeAngleSensorEventListener);
+ }
}
boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
@@ -1755,6 +1861,7 @@
boolean shouldRevertOverriddenRotation() {
// When transitioning to open.
return mDeviceState == DeviceStateController.DeviceState.OPEN
+ && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
&& mInHalfFoldTransition
&& mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
&& mUserRotationMode
@@ -1801,6 +1908,80 @@
UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
FOLDING_RECOMPUTE_CONFIG_DELAY_MS);
}
+
+ boolean shouldIgnoreSensorRotation() {
+ return mShouldIgnoreSensorRotation;
+ }
+
+ boolean shouldDisableRotationSensor() {
+ return mShouldDisableRotationSensor;
+ }
+
+ private void updateSensorRotationBlockIfNeeded() {
+ final long currentTime = uptimeMillis();
+ final boolean newShouldIgnoreRotation =
+ currentTime - mLastDisplaySwitchTime < mDisplaySwitchRotationBlockTimeMs
+ || currentTime - mLastHingeAngleEventTime < mHingeAngleRotationBlockTimeMs;
+
+ if (newShouldIgnoreRotation != mShouldIgnoreSensorRotation) {
+ mShouldIgnoreSensorRotation = newShouldIgnoreRotation;
+
+ // Resuming the autorotation
+ if (!mShouldIgnoreSensorRotation) {
+ if (mShouldDisableRotationSensor) {
+ // Sensor was disabled, let's re-enable it
+ mShouldDisableRotationSensor = false;
+ updateOrientationListenerLw();
+ } else {
+ // Sensor was not disabled, let's update the rotation in case if we received
+ // some rotation sensor updates when autorotate was disabled
+ updateRotationAndSendNewConfigIfChanged();
+ }
+ }
+ }
+ }
+
+ void onPhysicalDisplayChanged() {
+ if (!mPauseAutorotationDuringUnfolding) return;
+
+ mLastDisplaySwitchTime = uptimeMillis();
+
+ final boolean isUnfolding =
+ mDeviceState == DeviceStateController.DeviceState.OPEN
+ || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
+
+ if (isUnfolding) {
+ // Temporary disable rotation sensor updates when unfolding
+ mShouldDisableRotationSensor = true;
+ updateOrientationListenerLw();
+ }
+
+ updateSensorRotationBlockIfNeeded();
+ getHandler().postDelayed(() -> {
+ synchronized (mLock) {
+ updateSensorRotationBlockIfNeeded();
+ };
+ }, mDisplaySwitchRotationBlockTimeMs);
+ }
+
+ void onHingeAngleChanged(float hingeAngle) {
+ if (hingeAngle < mMaxHingeAngle) {
+ mLastHingeAngleEventTime = uptimeMillis();
+
+ updateSensorRotationBlockIfNeeded();
+
+ getHandler().postDelayed(() -> {
+ synchronized (mLock) {
+ updateSensorRotationBlockIfNeeded();
+ };
+ }, mHingeAngleRotationBlockTimeMs);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mService.mH;
}
private class OrientationListener extends WindowOrientationListener implements Runnable {
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index d65f464..44d6768 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -292,7 +292,9 @@
private void handleTap(boolean grantFocus) {
if (mInputChannel != null) {
if (mHostWindowState != null) {
- mWmService.grantEmbeddedWindowFocus(mSession, mHostWindowState.mClient,
+ // Use null session since this is being granted by system server and doesn't
+ // require the host session to be passed in
+ mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient,
mFocusGrantToken, grantFocus);
if (grantFocus) {
// If granting focus to the embedded when tapped, we need to ensure the host
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bb50372..bf511adf0 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -263,8 +263,8 @@
boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
params.mDisplayUniqueId = info.uniqueId;
- changed |= params.mWindowingMode != task.getTaskDisplayArea().getWindowingMode();
- params.mWindowingMode = task.getTaskDisplayArea().getWindowingMode();
+ changed |= params.mWindowingMode != task.getWindowingMode();
+ params.mWindowingMode = task.getWindowingMode();
if (task.mLastNonFullscreenBounds != null) {
changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index b0c384d..93233dd 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -50,6 +50,7 @@
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_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -235,9 +236,14 @@
private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
@Nullable
+ private final Boolean mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected;
+
+ @Nullable
private final Boolean mBooleanPropertyFakeFocus;
- private boolean mIsRelauchingAfterRequestedOrientationChanged;
+ private boolean mIsRelaunchingAfterRequestedOrientationChanged;
+
+ private boolean mLastShouldShowLetterboxUi;
private boolean mDoubleTapEvent;
@@ -253,6 +259,10 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
+ PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED);
mBooleanPropertyFakeFocus =
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isCompatFakeFocusEnabled,
@@ -387,7 +397,7 @@
::isPolicyForIgnoringRequestedOrientationEnabled,
mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled,
mBooleanPropertyIgnoreRequestedOrientation)) {
- if (mIsRelauchingAfterRequestedOrientationChanged) {
+ if (mIsRelaunchingAfterRequestedOrientationChanged) {
Slog.w(TAG, "Ignoring orientation update to "
+ screenOrientationToString(requestedOrientation)
+ " due to relaunching after setRequestedOrientation for "
@@ -422,6 +432,8 @@
*
* <p>This treatment is enabled when the following conditions are met:
* <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Opt-out component property isn't enabled
* <li>Per-app override is enabled
* <li>App has requested orientation more than 2 times within 1-second
* timer and activity is not letterboxed for fixed orientation
@@ -429,7 +441,11 @@
*/
@VisibleForTesting
boolean shouldIgnoreOrientationRequestLoop() {
- if (!mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled) {
+ if (!shouldEnableWithOptInOverrideAndOptOutProperty(
+ /* gatingCondition */ mLetterboxConfiguration
+ ::isPolicyForIgnoringRequestedOrientationEnabled,
+ mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled,
+ mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected)) {
return false;
}
@@ -476,8 +492,8 @@
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
- void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) {
- mIsRelauchingAfterRequestedOrientationChanged = isRelaunching;
+ void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) {
+ mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching;
}
/**
@@ -1154,12 +1170,28 @@
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+ if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) {
+ return mLastShouldShowLetterboxUi;
+ }
+
+ final boolean shouldShowLetterboxUi =
+ (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox background.
&& (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
+
+ mLastShouldShowLetterboxUi = shouldShowLetterboxUi;
+
+ return shouldShowLetterboxUi;
+ }
+
+ @VisibleForTesting
+ boolean isSurfaceReadyToShow(WindowState mainWindow) {
+ return mainWindow.isDrawn() // Regular case
+ // Waiting for relayoutWindow to call preserveSurface
+ || mainWindow.isDragResizeChanged();
}
@VisibleForTesting
@@ -1297,6 +1329,10 @@
return null;
}
+ boolean getIsRelaunchingAfterRequestedOrientationChanged() {
+ return mIsRelaunchingAfterRequestedOrientationChanged;
+ }
+
private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index b386665..f8f0211 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -338,7 +338,8 @@
synchronized (mService.mGlobalLock) {
final Task focusedStack = mService.getTopDisplayFocusedRootTask();
final Task topTask = focusedStack != null ? focusedStack.getTopMostTask() : null;
- resetFreezeTaskListReordering(topTask);
+ final Task reorderToEndTask = topTask != null && topTask.hasChild() ? topTask : null;
+ resetFreezeTaskListReordering(reorderToEndTask);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index db44532..0857898 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -481,8 +481,6 @@
// to layout without loading all the task snapshots
final PersistedTaskSnapshotData mLastTaskSnapshotData;
- private final Rect mTmpDimBoundsRect = new Rect();
-
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -3254,22 +3252,24 @@
@Override
void prepareSurfaces() {
- mDimmer.resetDimStates();
+ final Rect dimBounds = mDimmer.resetDimStates();
super.prepareSurfaces();
- getDimBounds(mTmpDimBoundsRect);
- // Bounds need to be relative, as the dim layer is a child.
- if (inFreeformWindowingMode()) {
- getBounds(mTmpRect);
- mTmpDimBoundsRect.offsetTo(mTmpDimBoundsRect.left - mTmpRect.left,
- mTmpDimBoundsRect.top - mTmpRect.top);
- } else {
- mTmpDimBoundsRect.offsetTo(0, 0);
+ if (dimBounds != null) {
+ getDimBounds(dimBounds);
+
+ // Bounds need to be relative, as the dim layer is a child.
+ if (inFreeformWindowingMode()) {
+ getBounds(mTmpRect);
+ dimBounds.offsetTo(dimBounds.left - mTmpRect.left, dimBounds.top - mTmpRect.top);
+ } else {
+ dimBounds.offsetTo(0, 0);
+ }
}
final SurfaceControl.Transaction t = getSyncTransaction();
- if (mDimmer.updateDims(t, mTmpDimBoundsRect)) {
+ if (dimBounds != null && mDimmer.updateDims(t)) {
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 76759ba..b0a879e 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1883,7 +1883,7 @@
// Only allow to specify orientation if this TDA is the last focused one on this logical
// display that can request orientation request.
return mDisplayContent.getOrientationRequestingTaskDisplayArea() == this
- && !getIgnoreOrientationRequest(orientation);
+ && !shouldIgnoreOrientationRequest(orientation);
}
void clearPreferredTopFocusableRootTask() {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 612fc4b..1d232fe 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2923,14 +2923,15 @@
return;
}
- mDimmer.resetDimStates();
+ final Rect dimBounds = mDimmer.resetDimStates();
super.prepareSurfaces();
- // Bounds need to be relative, as the dim layer is a child.
- final Rect dimBounds = getBounds();
- dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
- if (mDimmer.updateDims(getSyncTransaction(), dimBounds)) {
- scheduleAnimation();
+ if (dimBounds != null) {
+ // Bounds need to be relative, as the dim layer is a child.
+ dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ if (mDimmer.updateDims(getSyncTransaction())) {
+ scheduleAnimation();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0fe1f92..3cc1548 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -33,8 +33,6 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -167,6 +165,9 @@
private SurfaceControl.Transaction mStartTransaction = null;
private SurfaceControl.Transaction mFinishTransaction = null;
+ /** Used for failsafe clean-up to prevent leaks due to misbehaving player impls. */
+ private SurfaceControl.Transaction mCleanupTransaction = null;
+
/**
* Contains change infos for both participants and all remote-animatable ancestors. The
* ancestors can be the promotion candidates so their start-states need to be captured.
@@ -787,6 +788,24 @@
}
/**
+ * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots).
+ * This will ALWAYS be applied on transition finish just in-case
+ */
+ private static void buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (c.getSnapshot() != null) {
+ t.reparent(c.getSnapshot(), null);
+ }
+ }
+ for (int i = info.getRootCount() - 1; i >= 0; --i) {
+ final SurfaceControl leash = info.getRoot(i).getLeash();
+ if (leash == null) continue;
+ t.reparent(leash, null);
+ }
+ }
+
+ /**
* Set whether this transition can start a pip-enter transition when finished. This is usually
* true, but gets set to false when recents decides that it wants to finish its animation but
* not actually finish its animation (yeah...).
@@ -863,6 +882,10 @@
if (mStartTransaction != null) mStartTransaction.close();
if (mFinishTransaction != null) mFinishTransaction.close();
mStartTransaction = mFinishTransaction = null;
+ if (mCleanupTransaction != null) {
+ mCleanupTransaction.apply();
+ mCleanupTransaction = null;
+ }
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
}
@@ -1176,13 +1199,12 @@
if (primaryDisplay.isKeyguardLocked()) {
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
- // Check whether the participants were animated from back navigation.
- mController.mAtm.mBackNavigationController.onTransactionReady(this);
-
collectOrderChanges();
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
+ // Check whether the participants were animated from back navigation.
+ mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
@@ -1286,6 +1308,8 @@
}
}
buildFinishTransaction(mFinishTransaction, info);
+ mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
+ buildCleanupTransaction(mCleanupTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
@@ -1385,6 +1409,10 @@
ci.mSnapshot.release();
}
}
+ if (mCleanupTransaction != null) {
+ mCleanupTransaction.apply();
+ mCleanupTransaction = null;
+ }
}
/** The transition is ready to play. Make the start transaction show the surfaces. */
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0b9ceea..7ceac4f 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -46,6 +46,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
@@ -56,7 +57,6 @@
import android.view.animation.Animation;
import android.window.ScreenCapture;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
@@ -122,7 +122,7 @@
private boolean mShouldOffsetWallpaperCenter;
- final boolean mEnableSeparateLockScreenEngine;
+ final boolean mIsLockscreenLiveWallpaperEnabled;
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
if ((w.mAttrs.type == TYPE_WALLPAPER)) {
@@ -259,8 +259,8 @@
mShouldOffsetWallpaperCenter =
resources.getBoolean(
com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
- mEnableSeparateLockScreenEngine =
- resources.getBoolean(R.bool.config_independentLockscreenLiveWallpaper);
+ mIsLockscreenLiveWallpaperEnabled =
+ SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
}
void resetLargestDisplay(Display display) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 17ab551..6c38c6f 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -76,7 +76,7 @@
return;
}
mShowWhenLocked = showWhenLocked;
- if (mDisplayContent.mWallpaperController.mEnableSeparateLockScreenEngine) {
+ if (mDisplayContent.mWallpaperController.mIsLockscreenLiveWallpaperEnabled) {
// Move the window token to the front (private) or back (showWhenLocked). This is
// possible
// because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c34aa2b..85c601f 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -45,6 +45,8 @@
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
import static com.android.server.wm.WindowManagerService.MY_PID;
+import static java.util.Objects.requireNonNull;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -559,14 +561,19 @@
* @see BackgroundLaunchProcessController#addOrUpdateAllowBackgroundStartPrivileges(Binder,
* BackgroundStartPrivileges)
*/
- public void addOrUpdateBackgroundStartPrivileges(Binder entity,
- BackgroundStartPrivileges backgroundStartPrivileges) {
+ public void addOrUpdateBackgroundStartPrivileges(@NonNull Binder entity,
+ @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
+ requireNonNull(entity, "entity");
+ requireNonNull(backgroundStartPrivileges, "backgroundStartPrivileges");
+ checkArgument(backgroundStartPrivileges.allowsAny(),
+ "backgroundStartPrivileges does not allow anything");
mBgLaunchController.addOrUpdateAllowBackgroundStartPrivileges(entity,
backgroundStartPrivileges);
}
/** @see BackgroundLaunchProcessController#removeAllowBackgroundStartPrivileges(Binder) */
- public void removeBackgroundStartPrivileges(Binder entity) {
+ public void removeBackgroundStartPrivileges(@NonNull Binder entity) {
+ requireNonNull(entity, "entity");
mBgLaunchController.removeAllowBackgroundStartPrivileges(entity);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 23934e0..6e3924b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5177,13 +5177,14 @@
@Override
void prepareSurfaces() {
mIsDimming = false;
- applyDims();
- updateSurfacePositionNonOrganized();
- // Send information to SurfaceFlinger about the priority of the current window.
- updateFrameRateSelectionPriorityIfNeeded();
- updateScaleIfNeeded();
-
- mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+ if (mHasSurface) {
+ applyDims();
+ updateSurfacePositionNonOrganized();
+ // Send information to SurfaceFlinger about the priority of the current window.
+ updateFrameRateSelectionPriorityIfNeeded();
+ updateScaleIfNeeded();
+ mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+ }
super.prepareSurfaces();
}
@@ -5230,8 +5231,17 @@
if (surfaceInsetsChanged) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
}
- if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation()
- && okToDisplay() && mSyncState == SYNC_STATE_NONE) {
+ final boolean surfaceResizedWithoutMoveAnimation = surfaceSizeChanged
+ && mWinAnimator.getShown() && !canPlayMoveAnimation() && okToDisplay()
+ && mSyncState == SYNC_STATE_NONE;
+ final ActivityRecord activityRecord = getActivityRecord();
+ // If this window belongs to an activity that is relaunching due to an orientation
+ // change then delay the position update until it has redrawn to avoid any flickers.
+ final boolean isLetterboxedAndRelaunching = activityRecord != null
+ && activityRecord.areBoundsLetterboxed()
+ && activityRecord.mLetterboxUiController
+ .getIsRelaunchingAfterRequestedOrientationChanged();
+ if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) {
applyWithNextDraw(mSetSurfacePositionConsumer);
} else {
mSetSurfacePositionConsumer.accept(t);
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index dce7b87..5c77aa2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -30,6 +30,7 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Set;
/**
* Central session for a single clearCredentialState request. This class listens to the
@@ -40,12 +41,15 @@
implements ProviderSession.ProviderInternalCallback<Void> {
private static final String TAG = "GetRequestSession";
- public ClearRequestSession(Context context, int userId, int callingUid,
+ public ClearRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
+ Object lock, int userId, int callingUid,
IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
- CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
+ CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
+ CancellationSignal cancellationSignal,
long startedTimestamp) {
- super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
- callingAppInfo, cancellationSignal, startedTimestamp);
+ super(context, sessionCallback, lock, userId, callingUid, request, callback,
+ RequestInfo.TYPE_UNDEFINED,
+ callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
}
/**
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 98dc8ab..02aaf86 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -38,6 +38,7 @@
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
+import java.util.Set;
/**
* Central session for a single {@link CredentialManager#createCredential} request.
@@ -49,14 +50,17 @@
implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
private static final String TAG = "CreateRequestSession";
- CreateRequestSession(@NonNull Context context, int userId, int callingUid,
+ CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback,
+ Object lock, int userId, int callingUid,
CreateCredentialRequest request,
ICreateCredentialCallback callback,
CallingAppInfo callingAppInfo,
+ Set<ComponentName> enabledProviders,
CancellationSignal cancellationSignal,
long startedTimestamp) {
- super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
- callingAppInfo, cancellationSignal, startedTimestamp);
+ super(context, sessionCallback, lock, userId, callingUid, request, callback,
+ RequestInfo.TYPE_CREATE,
+ callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
}
/**
@@ -83,6 +87,7 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
+ mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
@@ -93,6 +98,7 @@
providerDataList));
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
+ mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
respondToClientWithErrorAndFinish(
CreateCredentialException.TYPE_UNKNOWN,
"Unable to invoke selector");
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index de06d44..9320dd2 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -33,7 +33,6 @@
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
-import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
@@ -50,6 +49,7 @@
import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -70,9 +70,11 @@
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -94,6 +96,8 @@
private static final String PERMISSION_DENIED_ERROR = "permission_denied";
private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR =
"Caller is missing WRITE_SECURE_SETTINGS permission";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
+ "enable_credential_manager";
private final Context mContext;
@@ -102,6 +106,13 @@
private final SparseArray<List<CredentialManagerServiceImpl>> mSystemServicesCacheList =
new SparseArray<>();
+ /** Cache of all ongoing request sessions per user id. */
+ @GuardedBy("mLock")
+ private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions =
+ new SparseArray<>();
+
+ private final SessionManager mSessionManager = new SessionManager();
+
public CredentialManagerService(@NonNull Context context) {
super(
context,
@@ -331,7 +342,7 @@
@NonNull
private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
- getFilteredResultFromRegistry(List<CredentialOption> options) {
+ getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -389,14 +400,6 @@
return providerSessions;
}
- private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
- return CredentialProviderInfoFactory.getCredentialProviderServices(
- mContext,
- userId,
- CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
- new HashSet<>());
- }
-
@Override
@GuardedBy("CredentialDescriptionRegistry.sLock")
public void onUserStopped(@NonNull TargetUser user) {
@@ -448,13 +451,17 @@
final GetRequestSession session =
new GetRequestSession(
getContext(),
+ mSessionManager,
+ mLock,
userId,
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+ getEnabledProviders(),
CancellationSignal.fromTransport(cancelTransport),
timestampBegan);
+ addSessionLocked(userId, session);
List<ProviderSession> providerSessions =
prepareProviderSessions(request, session);
@@ -499,11 +506,14 @@
final PrepareGetRequestSession session =
new PrepareGetRequestSession(
getContext(),
+ mSessionManager,
+ mLock,
userId,
callingUid,
getCredentialCallback,
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+ getEnabledProviders(),
CancellationSignal.fromTransport(cancelTransport),
timestampBegan,
prepareGetCredentialCallback);
@@ -515,8 +525,8 @@
// TODO: fix
prepareGetCredentialCallback.onResponse(
new PrepareGetCredentialResponseInternal(
- false, null,
- false, false, null));
+ false, null,
+ false, false, null));
} catch (RemoteException e) {
Log.i(
TAG,
@@ -540,10 +550,10 @@
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
.filter(credentialOption -> credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption
- .SUPPORTED_ELEMENT_KEYS) != null)
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS) != null)
.toList();
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
@@ -614,13 +624,17 @@
final CreateRequestSession session =
new CreateRequestSession(
getContext(),
+ mSessionManager,
+ mLock,
userId,
callingUid,
request,
callback,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+ getEnabledProviders(),
CancellationSignal.fromTransport(cancelTransport),
timestampBegan);
+ addSessionLocked(userId, session);
processCreateCredential(request, callback, session);
return cancelTransport;
@@ -775,6 +789,19 @@
mContext, userId, providerFilter, getEnabledProviders());
}
+ @Override
+ public boolean isServiceEnabled() {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL,
+ DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
+ false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
@SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
// this.mLock
private Set<ComponentName> getEnabledProviders() {
@@ -815,13 +842,17 @@
final ClearRequestSession session =
new ClearRequestSession(
getContext(),
+ mSessionManager,
+ mLock,
userId,
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId, null),
+ getEnabledProviders(),
CancellationSignal.fromTransport(cancelTransport),
timestampBegan);
+ addSessionLocked(userId, session);
// Initiate all provider sessions
// TODO: Determine if provider needs to have clear capability in their manifest
@@ -905,6 +936,13 @@
}
}
+ private void addSessionLocked(@UserIdInt int userId,
+ RequestSession requestSession) {
+ synchronized (mLock) {
+ mSessionManager.addSession(userId, requestSession.mRequestId, requestSession);
+ }
+ }
+
private void enforceCallingPackage(String callingPackage, int callingUid) {
int packageUid;
PackageManager pm = mContext.createContextAsUser(
@@ -919,4 +957,23 @@
throw new SecurityException(callingPackage + " does not belong to uid " + callingUid);
}
}
+
+ private class SessionManager implements RequestSession.SessionLifetime {
+ @Override
+ @GuardedBy("mLock")
+ public void onFinishRequestSession(@UserIdInt int userId, IBinder token) {
+ Log.i(TAG, "In onFinishRequestSession");
+ if (mRequestSessions.get(userId) != null) {
+ mRequestSessions.get(userId).remove(token);
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void addSession(int userId, IBinder token, RequestSession requestSession) {
+ if (mRequestSessions.get(userId) == null) {
+ mRequestSessions.put(userId, new HashMap<>());
+ }
+ mRequestSessions.get(userId).put(token, requestSession);
+ }
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 546c37f..8750906 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -20,7 +20,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.ui.DisabledProviderData;
@@ -30,6 +29,7 @@
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.service.credentials.CredentialProviderInfoFactory;
@@ -37,20 +37,32 @@
import android.util.Slog;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.UUID;
-import java.util.stream.Collectors;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
private static final String TAG = "CredentialManagerUi";
@NonNull
private final CredentialManagerUiCallback mCallbacks;
- @NonNull private final Context mContext;
+ @NonNull
+ private final Context mContext;
// TODO : Use for starting the activity for this user
private final int mUserId;
- @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
+
+ private UiStatus mStatus;
+
+ private final Set<ComponentName> mEnabledProviders;
+
+ enum UiStatus {
+ IN_PROGRESS,
+ USER_INTERACTION,
+ NOT_STARTED, TERMINATED
+ }
+
+ @NonNull
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -61,6 +73,7 @@
private void handleUiResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION:
+ mStatus = UiStatus.IN_PROGRESS;
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
@@ -70,75 +83,90 @@
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
+ mStatus = UiStatus.TERMINATED;
mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+ mStatus = UiStatus.TERMINATED;
mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
break;
case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
+ mStatus = UiStatus.TERMINATED;
mCallbacks.onUiSelectorInvocationFailure();
break;
default:
Slog.i(TAG, "Unknown error code returned from the UI");
+ mStatus = UiStatus.IN_PROGRESS;
mCallbacks.onUiSelectorInvocationFailure();
break;
}
}
+ /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
+ public Intent createCancelIntent(IBinder requestId, String packageName) {
+ return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
+ packageName);
+ }
+
/**
* Interface to be implemented by any class that wishes to get callbacks from the UI.
*/
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
void onUiSelection(UserSelectionDialogResult selection);
+
/** Called when the UI is canceled without a successful provider result. */
void onUiCancellation(boolean isUserCancellation);
/** Called when the selector UI fails to come up (mostly due to parsing issue today). */
void onUiSelectorInvocationFailure();
}
+
public CredentialManagerUi(Context context, int userId,
- CredentialManagerUiCallback callbacks) {
+ CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) {
Log.i(TAG, "In CredentialManagerUi constructor");
mContext = context;
mUserId = userId;
mCallbacks = callbacks;
+ mEnabledProviders = enabledProviders;
+ mStatus = UiStatus.IN_PROGRESS;
+ }
+
+ /** Set status for credential manager UI */
+ public void setStatus(UiStatus status) {
+ mStatus = status;
+ }
+
+ /** Returns status for credential manager UI */
+ public UiStatus getStatus() {
+ return mStatus;
}
/**
* Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
* by the calling app process.
- * @param requestInfo the information about the request
+ *
+ * @param requestInfo the information about the request
* @param providerDataList the list of provider data from remote providers
*/
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In createPendingIntent");
- ArrayList<DisabledProviderData> disabledProviderDataList = new ArrayList<>();
- Set<String> enabledProviders = providerDataList.stream()
- .map(ProviderData::getProviderFlattenedComponentName)
- .collect(Collectors.toUnmodifiableSet());
- Set<String> allProviders =
+ List<CredentialProviderInfo> allProviders =
CredentialProviderInfoFactory.getCredentialProviderServices(
- mContext,
- mUserId,
- CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
- new HashSet<>())
- .stream()
- .map(CredentialProviderInfo::getServiceInfo)
- .map(ServiceInfo::getComponentName)
- .map(ComponentName::flattenToString)
- .collect(Collectors.toUnmodifiableSet());
+ mContext,
+ mUserId,
+ CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
+ mEnabledProviders);
- for (String provider: allProviders) {
- if (!enabledProviders.contains(provider)) {
- disabledProviderDataList.add(new DisabledProviderData(provider));
- }
- }
+ List<DisabledProviderData> disabledProviderDataList = allProviders.stream()
+ .filter(provider -> !provider.isEnabled())
+ .map(disabledProvider -> new DisabledProviderData(
+ disabledProvider.getComponentName().flattenToString())).toList();
Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
- disabledProviderDataList, mResultReceiver)
+ new ArrayList<>(disabledProviderDataList), mResultReceiver)
.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c0c7be9..c44e665 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -35,6 +35,7 @@
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -45,21 +46,26 @@
IGetCredentialCallback, GetCredentialResponse>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
- public GetRequestSession(Context context, int userId, int callingUid,
+
+ public GetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
+ Object lock, int userId, int callingUid,
IGetCredentialCallback callback, GetCredentialRequest request,
- CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
+ CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
+ CancellationSignal cancellationSignal,
long startedTimestamp) {
- super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET,
- callingAppInfo, cancellationSignal, startedTimestamp);
+ super(context, sessionCallback, lock, userId, callingUid, request, callback,
+ RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal,
+ startedTimestamp);
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
- Collectors.toSet())).size(); // Dedupe type strings
+ Collectors.toSet())).size(); // Dedupe type strings
mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
}
/**
* Creates a new provider session, and adds it list of providers that are contributing to
* this session.
+ *
* @return the provider session created within this request session, for the given provider
* info.
*/
@@ -81,6 +87,7 @@
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
+ mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
try {
mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
@@ -88,6 +95,7 @@
providerDataList));
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
+ mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
respondToClientWithErrorAndFinish(
GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
}
@@ -146,7 +154,7 @@
@Override
public void onUiSelectorInvocationFailure() {
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
- "No credentials available.");
+ "No credentials available.");
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index c48654a..703ab7c 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -108,7 +108,7 @@
browsedProviderUid[index] = metric.getProviderUid();
index++;
}
- FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE,
+ FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED,
/* session_id */ finalPhaseMetric.getSessionId(),
/* sequence_num */ emitSequenceId,
/* ui_returned_final_start */ finalPhaseMetric.isUiReturned(),
@@ -204,7 +204,7 @@
candidateRemoteEntryCountList[index] = metric.getRemoteEntryCount();
index++;
}
- FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_CANDIDATE_PHASE,
+ FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED,
/* session_id */ sessionId,
/* sequence_num */ emitSequenceId,
/* query_returned */ queryReturned,
@@ -277,7 +277,7 @@
if (!LOG_FLAG) {
return;
}
- FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE,
+ FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE_REPORTED,
/* api_name */ initialPhaseMetric.getApiName(),
/* caller_uid */ initialPhaseMetric.getCallerUid(),
/* session_id */ initialPhaseMetric.getSessionId(),
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index c4e480a..f274e65 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -49,14 +49,14 @@
private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback;
- public PrepareGetRequestSession(Context context, int userId, int callingUid,
- IGetCredentialCallback callback,
- GetCredentialRequest request,
- CallingAppInfo callingAppInfo,
+ public PrepareGetRequestSession(Context context,
+ RequestSession.SessionLifetime sessionCallback, Object lock, int userId,
+ int callingUid, IGetCredentialCallback getCredCallback, GetCredentialRequest request,
+ CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
CancellationSignal cancellationSignal, long startedTimestamp,
IPrepareGetCredentialCallback prepareGetCredentialCallback) {
- super(context, userId, callingUid, callback, request, callingAppInfo, cancellationSignal,
- startedTimestamp);
+ super(context, sessionCallback, lock, userId, callingUid, getCredCallback, request,
+ callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 1b736e0..0c3d3f4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -23,6 +23,7 @@
import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
import android.util.Log;
@@ -109,6 +110,11 @@
}
}
+ @Override
+ public void onProviderCancellable(ICancellationSignal cancellation) {
+ mProviderCancellationSignal = cancellation;
+ }
+
@Nullable
@Override
protected ProviderData prepareUiData() {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index bef045f..8b9255a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -29,6 +29,7 @@
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.Bundle;
+import android.os.ICancellationSignal;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CallingAppInfo;
@@ -173,6 +174,11 @@
}
}
+ @Override
+ public void onProviderCancellable(ICancellationSignal cancellation) {
+ mProviderCancellationSignal = cancellation;
+ }
+
private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
@@ -236,7 +242,7 @@
protected void invokeSession() {
if (mRemoteCredentialService != null) {
startCandidateMetrics();
- mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
+ mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 427a894..8d3d064 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,6 +30,7 @@
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
import android.service.credentials.Action;
import android.service.credentials.BeginGetCredentialOption;
import android.service.credentials.BeginGetCredentialRequest;
@@ -235,6 +236,11 @@
}
}
+ @Override
+ public void onProviderCancellable(ICancellationSignal cancellation) {
+ mProviderCancellationSignal = cancellation;
+ }
+
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 9cf2721..24292ef2 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -29,6 +29,7 @@
import android.credentials.ui.GetCredentialProviderData;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderService;
@@ -115,7 +116,7 @@
@NonNull String servicePackageName,
@NonNull CredentialOption requestOption) {
super(context, requestOption, session,
- new ComponentName(servicePackageName, servicePackageName) ,
+ new ComponentName(servicePackageName, servicePackageName),
userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
@@ -132,7 +133,7 @@
@NonNull String servicePackageName,
@NonNull CredentialOption requestOption) {
super(context, requestOption, session,
- new ComponentName(servicePackageName, servicePackageName) ,
+ new ComponentName(servicePackageName, servicePackageName),
userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
@@ -255,13 +256,18 @@
}
@Override
+ public void onProviderCancellable(ICancellationSignal cancellation) {
+ // No need to do anything since this class does not rely on a remote service.
+ }
+
+ @Override
protected void invokeSession() {
mProviderResponse = mCredentialDescriptionRegistry
.getFilteredResultForProvider(mCredentialProviderPackageName,
mElementKeys);
mCredentialEntries = mProviderResponse.stream().flatMap(
- (Function<CredentialDescriptionRegistry.FilterResult,
- Stream<CredentialEntry>>) filterResult
+ (Function<CredentialDescriptionRegistry.FilterResult,
+ Stream<CredentialEntry>>) filterResult
-> filterResult.mCredentialEntries.stream())
.collect(Collectors.toList());
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 8c0e1c1..d165756 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -30,6 +30,7 @@
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -189,7 +190,7 @@
}
setStatus(Status.CANCELED);
} catch (RemoteException e) {
- Log.i(TAG, "Issue while cancelling provider session: " + e.getMessage());
+ Slog.e(TAG, "Issue while cancelling provider session: ", e);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index ff4e3b6..c1e9bc6 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -82,6 +82,9 @@
/** Called when the remote provider service dies. */
void onProviderServiceDied(RemoteCredentialService service);
+
+ /** Called to set the cancellation transport from the remote provider service. */
+ void onProviderCancellable(ICancellationSignal cancellation);
}
public RemoteCredentialService(@NonNull Context context,
@@ -117,43 +120,56 @@
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderGetSession} class that maintains provider state
*/
- public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
+ public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
ProviderCallbacks<BeginGetCredentialResponse> callback) {
Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
+ new AtomicReference<>();
+
CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> {
CompletableFuture<BeginGetCredentialResponse> getCredentials =
new CompletableFuture<>();
final long originalCallingUidToken = Binder.clearCallingIdentity();
try {
- ICancellationSignal cancellationSignal =
- service.onBeginGetCredential(request,
- new IBeginGetCredentialCallback.Stub() {
- @Override
- public void onSuccess(BeginGetCredentialResponse response) {
- getCredentials.complete(response);
- }
+ service.onBeginGetCredential(request,
+ new IBeginGetCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(BeginGetCredentialResponse response) {
+ getCredentials.complete(response);
+ }
- @Override
- public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" : String.valueOf(
- message);
- getCredentials.completeExceptionally(
- new GetCredentialException(errorType, errorMsg));
- }
- });
- cancellationSink.set(cancellationSignal);
+ @Override
+ public void onFailure(String errorType, CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(
+ message);
+ getCredentials.completeExceptionally(
+ new GetCredentialException(errorType, errorMsg));
+ }
+
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<BeginGetCredentialResponse> future =
+ futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ callback.onProviderCancellable(cancellation);
+ }
+ }
+ });
return getCredentials;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
- return cancellationSink.get();
}
/**
@@ -164,10 +180,12 @@
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderCreateSession} class that maintains provider state
*/
- public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+ public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
ProviderCallbacks<BeginCreateCredentialResponse> callback) {
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
+ new AtomicReference<>();
CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
postAsync(service -> {
@@ -175,7 +193,7 @@
new CompletableFuture<>();
final long originalCallingUidToken = Binder.clearCallingIdentity();
try {
- ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+ service.onBeginCreateCredential(
request, new IBeginCreateCredentialCallback.Stub() {
@Override
public void onSuccess(BeginCreateCredentialResponse response) {
@@ -192,18 +210,28 @@
createCredentialFuture.completeExceptionally(
new CreateCredentialException(errorType, errorMsg));
}
+
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<BeginCreateCredentialResponse> future =
+ futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ callback.onProviderCancellable(cancellation);
+ }
+ }
});
- cancellationSink.set(cancellationSignal);
return createCredentialFuture;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
-
- return cancellationSink.get();
}
/**
@@ -214,10 +242,11 @@
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderClearSession} class that maintains provider state
*/
- public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+ public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
ProviderCallbacks<Void> callback) {
Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
CompletableFuture<Void> connectThenExecute =
postAsync(service -> {
@@ -225,7 +254,7 @@
new CompletableFuture<>();
final long originalCallingUidToken = Binder.clearCallingIdentity();
try {
- ICancellationSignal cancellationSignal = service.onClearCredentialState(
+ service.onClearCredentialState(
request, new IClearCredentialStateCallback.Stub() {
@Override
public void onSuccess() {
@@ -243,18 +272,27 @@
new ClearCredentialStateException(errorType,
errorMsg));
}
+
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<Void> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ callback.onProviderCancellable(cancellation);
+ }
+ }
});
- cancellationSink.set(cancellationSignal);
return clearCredentialFuture;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
-
- return cancellationSink.get();
}
private <T> void handleExecutionResponse(T result,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index cfb9ad4..e98c524 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -20,6 +20,7 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
@@ -29,8 +30,10 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.credentials.CallingAppInfo;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.R;
import com.android.server.credentials.metrics.ApiName;
@@ -39,8 +42,9 @@
import com.android.server.credentials.metrics.RequestSessionMetric;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Base class of a request session, that listens to UI events. This class must be extended
@@ -49,6 +53,11 @@
abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback {
private static final String TAG = "RequestSession";
+ public interface SessionLifetime {
+ /** Called when the user makes a selection. */
+ void onFinishRequestSession(@UserIdInt int userId, IBinder token);
+ }
+
// TODO: Revise access levels of attributes
@NonNull
protected final T mClientRequest;
@@ -72,10 +81,16 @@
@NonNull
protected final CancellationSignal mCancellationSignal;
- protected final Map<String, ProviderSession> mProviders = new HashMap<>();
+ protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>();
protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric();
protected final String mHybridService;
+ protected final Object mLock;
+
+ protected final SessionLifetime mSessionCallback;
+
+ private final Set<ComponentName> mEnabledProviders;
+
@NonNull
protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
@@ -91,26 +106,58 @@
}
protected RequestSession(@NonNull Context context,
- @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
+ RequestSession.SessionLifetime sessionCallback,
+ Object lock, @UserIdInt int userId, int callingUid,
+ @NonNull T clientRequest, U clientCallback,
@NonNull String requestType,
CallingAppInfo callingAppInfo,
+ Set<ComponentName> enabledProviders,
CancellationSignal cancellationSignal, long timestampStarted) {
mContext = context;
+ mLock = lock;
+ mSessionCallback = sessionCallback;
mUserId = userId;
mCallingUid = callingUid;
mClientRequest = clientRequest;
mClientCallback = clientCallback;
mRequestType = requestType;
mClientAppInfo = callingAppInfo;
+ mEnabledProviders = enabledProviders;
mCancellationSignal = cancellationSignal;
mHandler = new Handler(Looper.getMainLooper(), null, true);
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
- mUserId, this);
+ mUserId, this, mEnabledProviders);
mHybridService = context.getResources().getString(
R.string.config_defaultCredentialManagerHybridService);
mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId,
mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
+ setCancellationListener();
+ }
+
+ private void setCancellationListener() {
+ mCancellationSignal.setOnCancelListener(
+ () -> {
+ boolean isUiActive = maybeCancelUi();
+ finishSession(!isUiActive);
+ }
+ );
+ }
+
+ private boolean maybeCancelUi() {
+ if (mCredentialManagerUi.getStatus()
+ == CredentialManagerUi.UiStatus.USER_INTERACTION) {
+ final long originalCallingUidToken = Binder.clearCallingIdentity();
+ try {
+ mContext.startActivityAsUser(mCredentialManagerUi.createCancelIntent(
+ mRequestId, mClientAppInfo.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), UserHandle.of(mUserId));
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(originalCallingUidToken);
+ }
+ }
+ return false;
}
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -154,12 +201,19 @@
}
protected void finishSession(boolean propagateCancellation) {
- Log.i(TAG, "finishing session");
+ Slog.d(TAG, "finishing session with propagateCancellation " + propagateCancellation);
if (propagateCancellation) {
mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
}
mRequestSessionStatus = RequestSessionStatus.COMPLETE;
mProviders.clear();
+ clearRequestSessionLocked();
+ }
+
+ private void clearRequestSessionLocked() {
+ synchronized (mLock) {
+ mSessionCallback.onFinishRequestSession(mUserId, mRequestId);
+ }
}
protected boolean isAnyProviderPending() {
@@ -194,7 +248,6 @@
ArrayList<ProviderData> providerDataList = getProviderDataForUi();
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
- mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
launchUiWithProviderData(providerDataList);
}
}
@@ -204,9 +257,9 @@
Log.i(TAG, "In getProviderDataAndInitiateUi");
Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
ArrayList<ProviderData> providerDataList = new ArrayList<>();
+ mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
if (isSessionCancelled()) {
- mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
finishSession(/*propagateCancellation=*/true);
return providerDataList;
}
@@ -258,7 +311,7 @@
* Allows subclasses to directly finalize the call and set closing metrics on error completion.
*
* @param errorType the type of error given back in the flow
- * @param errorMsg the error message given back in the flow
+ * @param errorMsg the error message given back in the flow
*/
protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index f40e73e..ce84d9a 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -20,11 +20,11 @@
import static android.credentials.ui.RequestInfo.TYPE_GET;
import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CLEAR_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CREATE_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN;
import android.credentials.ui.RequestInfo;
import android.util.Log;
@@ -33,12 +33,14 @@
import java.util.Map;
public enum ApiName {
- UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN),
- GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL),
- CREATE_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CREATE_CREDENTIAL),
- CLEAR_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CLEAR_CREDENTIAL),
+ UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN),
+ GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL),
+ CREATE_CREDENTIAL(
+ CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL),
+ CLEAR_CREDENTIAL(
+ CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL),
IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE(
- CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
+CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
);
private static final String TAG = "ApiName";
@@ -58,7 +60,6 @@
this.mInnerMetricCode = innerMetricCode;
}
-
/**
* Gives the West-world version of the metric name.
*
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index 8fea369..4097765 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -16,18 +16,18 @@
package com.android.server.credentials.metrics;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_CLIENT_CANCELED;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_USER_CANCELED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED;
public enum ApiStatus {
- SUCCESS(CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_SUCCESS),
- FAILURE(CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_FAILURE),
+ SUCCESS(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_SUCCESS),
+ FAILURE(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_FAILURE),
CLIENT_CANCELED(
- CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_CLIENT_CANCELED),
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_CLIENT_CANCELED),
USER_CANCELED(
- CREDENTIAL_MANAGER_FINAL_PHASE__API_STATUS__API_STATUS_USER_CANCELED);
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__API_STATUS__API_STATUS_USER_CANCELED);
private final int mInnerMetricCode;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
index 440ac51..80f9fdc 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -16,11 +16,11 @@
package com.android.server.credentials.metrics;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__ACTION_ENTRY;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__AUTHENTICATION_ENTRY;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__CREDENTIAL_ENTRY;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__REMOTE_ENTRY;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__ACTION_ENTRY;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__AUTHENTICATION_ENTRY;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__CREDENTIAL_ENTRY;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__REMOTE_ENTRY;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__UNKNOWN;
import static com.android.server.credentials.ProviderGetSession.ACTION_ENTRY_KEY;
import static com.android.server.credentials.ProviderGetSession.AUTHENTICATION_ACTION_ENTRY_KEY;
import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
@@ -32,12 +32,12 @@
import java.util.Map;
public enum EntryEnum {
- UNKNOWN(CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__UNKNOWN),
- ACTION_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__ACTION_ENTRY),
- CREDENTIAL_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__CREDENTIAL_ENTRY),
- REMOTE_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__REMOTE_ENTRY),
+ UNKNOWN(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__UNKNOWN),
+ ACTION_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__ACTION_ENTRY),
+ CREDENTIAL_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__CREDENTIAL_ENTRY),
+ REMOTE_ENTRY(CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__REMOTE_ENTRY),
AUTHENTICATION_ENTRY(
- CREDENTIAL_MANAGER_FINAL_PHASE__CLICKED_ENTRIES__AUTHENTICATION_ENTRY
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CLICKED_ENTRIES__AUTHENTICATION_ENTRY
);
private static final String TAG = "EntryEnum";
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
index 83713ab..a12a694 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
@@ -16,24 +16,24 @@
package com.android.server.credentials.metrics;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_UNKNOWN;
public enum ProviderStatusForMetrics {
UNKNOWN(
- CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_UNKNOWN),
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_UNKNOWN),
FINAL_FAILURE(
- CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE),
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE),
QUERY_FAILURE(
- CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE),
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE),
FINAL_SUCCESS(
- CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS),
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS),
QUERY_SUCCESS(
- CREDENTIAL_MANAGER_FINAL_PHASE__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS);
+ CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED__CHOSEN_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS);
private final int mInnerMetricCode;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 474df98..950ec77 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -32,22 +32,25 @@
final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
+ private static final String ATTR_VALUE = "value";
+
+ private static final String TAG = "BooleanPolicySerializer";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
- @NonNull Boolean value)
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value)
throws IOException {
Objects.requireNonNull(value);
- serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
+ serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ BooleanPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new BooleanPolicyValue(
- parser.getAttributeBoolean(/* namespace= */ null, attributeName));
+ parser.getAttributeBoolean(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+ Log.e(TAG, "Error parsing Boolean policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index c79aac7..ee73f8a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -53,6 +53,10 @@
// rather than in its own files.
final class BundlePolicySerializer extends PolicySerializer<Bundle> {
+ private static final String TAG = "BundlePolicySerializer";
+
+ private static final String ATTR_FILE_NAME = "file-name";
+
private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_";
private static final String XML_SUFFIX = ".xml";
@@ -72,7 +76,7 @@
@Override
void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
- String attributeName, @NonNull Bundle value) throws IOException {
+ @NonNull Bundle value) throws IOException {
Objects.requireNonNull(value);
Objects.requireNonNull(policyKey);
if (!(policyKey instanceof PackagePolicyKey)) {
@@ -82,13 +86,13 @@
String packageName = ((PackagePolicyKey) policyKey).getPackageName();
String fileName = packageToRestrictionsFileName(packageName, value);
writeApplicationRestrictionsLAr(fileName, value);
- serializer.attribute(/* namespace= */ null, attributeName, fileName);
+ serializer.attribute(/* namespace= */ null, ATTR_FILE_NAME, fileName);
}
@Nullable
@Override
- BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
- String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName);
+ BundlePolicyValue readFromXml(TypedXmlPullParser parser) {
+ String fileName = parser.getAttributeValue(/* namespace= */ null, ATTR_FILE_NAME);
return new BundlePolicyValue(readApplicationRestrictions(fileName));
}
@@ -119,7 +123,7 @@
final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
XmlUtils.nextElement(parser);
if (parser.getEventType() != XmlPullParser.START_TAG) {
- Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file "
+ Slog.e(TAG, "Unable to read restrictions file "
+ restrictionsFile.getBaseFile());
return restrictions;
}
@@ -127,7 +131,7 @@
readEntry(restrictions, values, parser);
}
} catch (IOException | XmlPullParserException e) {
- Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ Slog.w(TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -209,7 +213,7 @@
restrictionsFile.finishWrite(fos);
} catch (Exception e) {
restrictionsFile.failWrite(fos);
- Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e);
+ Slog.e(TAG, "Error writing application restrictions list", e);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
index d1c6bcb..6303a1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -30,30 +30,31 @@
import java.util.Objects;
final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName> {
- private static final String ATTR_PACKAGE_NAME = ":package-name";
- private static final String ATTR_CLASS_NAME = ":class-name";
+
+ private static final String TAG = "ComponentNamePolicySerializer";
+
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_CLASS_NAME = "class-name";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull ComponentName value) throws IOException {
Objects.requireNonNull(value);
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGE_NAME, value.getPackageName());
+ /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName());
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_CLASS_NAME, value.getClassName());
+ /* namespace= */ null, ATTR_CLASS_NAME, value.getClassName());
}
@Nullable
@Override
- ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+ ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser) {
String packageName = parser.getAttributeValue(
- /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME);
+ /* namespace= */ null, ATTR_PACKAGE_NAME);
String className = parser.getAttributeValue(
- /* namespace= */ null, attributeNamePrefix + ATTR_CLASS_NAME);
+ /* namespace= */ null, ATTR_CLASS_NAME);
if (packageName == null || className == null) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy.");
+ Log.e(TAG, "Error parsing ComponentName policy.");
return null;
}
return new ComponentNamePolicyValue(new ComponentName(packageName, className));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 7eeb51c..f111a95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -23,7 +23,6 @@
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
-import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
import android.Manifest;
import android.annotation.NonNull;
@@ -47,7 +46,6 @@
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.DeviceConfig;
import android.telephony.TelephonyManager;
import android.util.AtomicFile;
import android.util.Log;
@@ -86,9 +84,6 @@
DevicePolicyIdentifiers.getIdentifierForUserRestriction(
UserManager.DISALLOW_CELLULAR_2G);
- private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
- private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = true;
-
private final Context mContext;
private final UserManager mUserManager;
@@ -771,28 +766,31 @@
Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
intent.setPackage(admin.getPackageName());
- List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
- intent,
- PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
- admin.getUserId());
- if (receivers.isEmpty()) {
- Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT"
- + "in package " + admin.getPackageName());
- return;
- }
+ Binder.withCleanCallingIdentity(() -> {
+ List<ResolveInfo> receivers =
+ mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT"
+ + "in package " + admin.getPackageName());
+ return;
+ }
- Bundle extras = new Bundle();
- policyDefinition.getPolicyKey().writeToBundle(extras);
- extras.putInt(
- EXTRA_POLICY_TARGET_USER_ID,
- getTargetUser(admin.getUserId(), userId));
- extras.putInt(
- EXTRA_POLICY_UPDATE_RESULT_KEY,
- result);
+ Bundle extras = new Bundle();
+ policyDefinition.getPolicyKey().writeToBundle(extras);
+ extras.putInt(
+ EXTRA_POLICY_TARGET_USER_ID,
+ getTargetUser(admin.getUserId(), userId));
+ extras.putInt(
+ EXTRA_POLICY_UPDATE_RESULT_KEY,
+ result);
- intent.putExtras(extras);
+ intent.putExtras(extras);
- maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
+ maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
+ });
}
// TODO(b/261430877): Finalise the decision on which admins to send the updates to.
@@ -821,27 +819,30 @@
Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_CHANGED);
intent.setPackage(admin.getPackageName());
- List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
- intent,
- PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
- admin.getUserId());
- if (receivers.isEmpty()) {
- Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED"
- + "in package " + admin.getPackageName());
- return;
- }
+ Binder.withCleanCallingIdentity(() -> {
+ List<ResolveInfo> receivers =
+ mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED"
+ + "in package " + admin.getPackageName());
+ return;
+ }
- Bundle extras = new Bundle();
- policyDefinition.getPolicyKey().writeToBundle(extras);
- extras.putInt(
- EXTRA_POLICY_TARGET_USER_ID,
- getTargetUser(admin.getUserId(), userId));
- extras.putInt(EXTRA_POLICY_UPDATE_RESULT_KEY, reason);
- intent.putExtras(extras);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Bundle extras = new Bundle();
+ policyDefinition.getPolicyKey().writeToBundle(extras);
+ extras.putInt(
+ EXTRA_POLICY_TARGET_USER_ID,
+ getTargetUser(admin.getUserId(), userId));
+ extras.putInt(EXTRA_POLICY_UPDATE_RESULT_KEY, reason);
+ intent.putExtras(extras);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- maybeSendIntentToAdminReceivers(
- intent, UserHandle.of(admin.getUserId()), receivers);
+ maybeSendIntentToAdminReceivers(
+ intent, UserHandle.of(admin.getUserId()), receivers);
+ });
}
private void maybeSendIntentToAdminReceivers(
@@ -1146,38 +1147,6 @@
return mEnforcingAdmins.size() > 0;
}
- /**
- * Returns {@code true} if the coexistence flag is enabled or:
- * <ul>
- * <li>If the provided package is an admin with existing policies
- * <li>A new admin and no other admin have policies set
- * <li>More than one admin have policies set
- */
- boolean canAdminAddPolicies(String packageName, int userId) {
- if (isCoexistenceFlagEnabled()) {
- return true;
- }
-
- if (mEnforcingAdmins.contains(userId)
- && mEnforcingAdmins.get(userId).stream().anyMatch(admin ->
- admin.getPackageName().equals(packageName))) {
- return true;
- }
-
- int numOfEnforcingAdmins = 0;
- for (int i = 0; i < mEnforcingAdmins.size(); i++) {
- numOfEnforcingAdmins += mEnforcingAdmins.get(i).size();
- }
- return numOfEnforcingAdmins == 0 || numOfEnforcingAdmins > 1;
- }
-
- private boolean isCoexistenceFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- ENABLE_COEXISTENCE_FLAG,
- DEFAULT_ENABLE_COEXISTENCE_FLAG);
- }
-
private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
if (!policyDefinition.getPolicyKey().getIdentifier().equals(
@@ -1212,7 +1181,8 @@
private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
- private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
+ private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
+ private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry";
private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
private static final String ATTR_USER_ID = "user-id";
@@ -1267,11 +1237,14 @@
serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
- policy.getKey().saveToXml(serializer);
- serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
+ policy.getKey().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
+
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
policy.getValue().saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
}
@@ -1284,11 +1257,13 @@
for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
policy.getKey().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
- serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
policy.getValue().saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
}
@@ -1354,28 +1329,56 @@
private void readLocalPoliciesInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
- PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
- if (!mLocalPolicies.contains(userId)) {
- mLocalPolicies.put(userId, new HashMap<>());
+ PolicyKey policyKey = null;
+ PolicyState<?> policyState = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_POLICY_KEY_ENTRY:
+ policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ break;
+ case TAG_POLICY_STATE_ENTRY:
+ policyState = PolicyState.readFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ }
}
- PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
- if (adminsPolicy != null) {
- mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
+
+ if (policyKey != null && policyState != null) {
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
+ }
+ mLocalPolicies.get(userId).put(policyKey, policyState);
} else {
- Log.e(TAG,
- "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+ Log.e(TAG, "Error parsing local policy");
}
}
private void readGlobalPoliciesInner(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
- PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
- if (adminsPolicy != null) {
- mGlobalPolicies.put(policyKey, adminsPolicy);
+ PolicyKey policyKey = null;
+ PolicyState<?> policyState = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_POLICY_KEY_ENTRY:
+ policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ break;
+ case TAG_POLICY_STATE_ENTRY:
+ policyState = PolicyState.readFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ }
+ }
+
+ if (policyKey != null && policyState != null) {
+ mGlobalPolicies.put(policyKey, policyState);
} else {
- Log.e(TAG,
- "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+ Log.e(TAG, "Error parsing global policy");
}
}
@@ -1387,20 +1390,5 @@
}
mEnforcingAdmins.get(admin.getUserId()).add(admin);
}
-
- @Nullable
- private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
- int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- String tag = parser.getName();
- if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
- return PolicyState.readFromXml(parser);
- }
- Log.e(TAG, "Unknown tag " + tag);
- }
- Log.e(TAG, "Error parsing file, AdminsPolicy not found");
- return null;
- }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3933766..94e6e73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -92,6 +92,8 @@
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
+import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY;
@@ -439,6 +441,7 @@
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
+import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -560,6 +563,8 @@
private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572;
+ private static final int MAX_PROFILE_NAME_LENGTH = 200;
+
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
@@ -773,6 +778,14 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
+ /**
+ * Apps targetting U+ should now expect that attempts to grant sensor permissions without
+ * authorisation will result in a security exception.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long THROW_SECURITY_EXCEPTION_FOR_SENSOR_PERMISSIONS = 277035314L;
+
// Only add to the end of the list. Do not change or rearrange these values, that will break
// historical data. Do not use negative numbers or zero, logger only handles positive
// integers.
@@ -836,18 +849,13 @@
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
- // ENABLE_DEVICE_POLICY_ENGINE_FLAG must be enabled before this could be enabled.
private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
"enable_permission_based_access";
private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
- // This must be enabled before PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG is enabled, the reason
- // we're not just relying on PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG to enable the policy engine
- // is that we might want to enable it before the permission changes are ready if we want to test
- // it on DPCs.
- // Once this is enabled, it can no longer be disabled in production
- private static final String ENABLE_DEVICE_POLICY_ENGINE_FLAG = "enable_device_policy_engine";
- private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG = false;
+ private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG =
+ "enable_device_policy_engine";
+ private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = false;
// TODO(b/265683382) remove the flag after rollout.
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
@@ -1414,10 +1422,7 @@
&& (owner.getPackageName().equals(packageName))) {
startOwnerService(userHandle, "package-broadcast");
}
- if (shouldMigrateToDevicePolicyEngine()) {
- migratePoliciesToDevicePolicyEngine();
- }
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.handlePackageChanged(packageName, userHandle);
}
// Persist updates if the removed package was an admin or delegate.
@@ -2121,7 +2126,7 @@
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
mDeviceManagementResourcesProvider.load();
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.load();
}
@@ -2617,7 +2622,7 @@
ActiveAdmin profileOwner, boolean newOwner) {
if (newOwner || mInjector.settingsSecureGetIntForUser(
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) {
- if (isDevicePolicyEngineEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.getPolicyDefinitionForUserRestriction(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
@@ -2645,7 +2650,7 @@
if (defaultRestrictions.equals(admin.defaultEnabledRestrictionsAlreadySet)) {
return; // The same set of default restrictions has been already applied.
}
- if (isDevicePolicyEngineEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
for (String restriction : defaultRestrictions) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction),
@@ -2827,6 +2832,16 @@
return doAdmin;
}
+ ActiveAdmin getDefaultDeviceOwnerLocked(@UserIdInt int userId) {
+ ensureLocked();
+ ComponentName doComponent = mOwners.getDeviceOwnerComponent();
+ if (mOwners.getDeviceOwnerType(doComponent.getPackageName()) == DEFAULT_DEVICE_OWNER) {
+ ActiveAdmin doAdmin = getUserData(userId).mAdminMap.get(doComponent);
+ return doAdmin;
+ }
+ return null;
+ }
+
ActiveAdmin getProfileOwnerLocked(@UserIdInt int userId) {
ensureLocked();
final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId);
@@ -2856,6 +2871,18 @@
return getDeviceOwnerLocked(userId);
}
+ ActiveAdmin getProfileOwnerOrDefaultDeviceOwnerLocked(@UserIdInt int userId) {
+ ensureLocked();
+ // Try to find an admin which can use reqPolicy
+ final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId);
+
+ if (poAdminComponent != null) {
+ return getProfileOwnerLocked(userId);
+ }
+
+ return getDefaultDeviceOwnerLocked(userId);
+ }
+
@NonNull ActiveAdmin getParentOfAdminIfRequired(ActiveAdmin admin, boolean parent) {
Objects.requireNonNull(admin);
return parent ? admin.getParentActiveAdmin() : admin;
@@ -3561,7 +3588,7 @@
}
startOwnerService(userId, "start-user");
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
}
}
@@ -3588,7 +3615,7 @@
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.handleUnlockUser(userId);
}
}
@@ -3600,7 +3627,7 @@
void handleStopUser(int userId) {
updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT));
mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user");
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.handleStopUser(userId);
}
}
@@ -3726,11 +3753,6 @@
synchronized (getLockObject()) {
checkActiveAdminPrecondition(adminReceiver, info, policy);
mInjector.binderWithCleanCallingIdentity(() -> {
- if (!canAddActiveAdminIfPolicyEngineEnabled(
- adminReceiver.getPackageName(), userHandle)) {
- throw new IllegalStateException("Can't add non-coexistable admin.");
- }
-
final ActiveAdmin existingAdmin
= getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (!refreshing && existingAdmin != null) {
@@ -5350,9 +5372,12 @@
saveSettingsLocked(caller.getUserId());
});
+
+ //TODO(b/276855301): caller.getPackageName() will be null when the coexistence flags are
+ // turned off. Change back to caller.getPackageName once this API is unflagged.
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY)
- .setAdmin(caller.getPackageName())
+ .setAdmin(admin.info.getPackageName())
.setInt(passwordComplexity)
.setBoolean(calledOnParent)
.write();
@@ -5987,8 +6012,13 @@
}
@Override
- public void lockNow(int flags, boolean parent) {
- final CallerIdentity caller = getCallerIdentity();
+ public void lockNow(int flags, String callerPackageName, boolean parent) {
+ CallerIdentity caller;
+ if (isPermissionCheckFlagEnabled()) {
+ caller = getCallerIdentity(callerPackageName);
+ } else {
+ caller = getCallerIdentity();
+ }
final int callingUserId = caller.getUserId();
ComponentName adminComponent = null;
@@ -5997,11 +6027,13 @@
// Make sure the caller has any active admin with the right policy or
// the required permission.
if (isPermissionCheckFlagEnabled()) {
- admin = getActiveAdminOrCheckPermissionsForCallerLocked(
- null,
- DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
- parent,
- Set.of(MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE));
+ admin = enforcePermissionAndGetEnforcingAdmin(
+ /* admin= */ null,
+ /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
+ USES_POLICY_FORCE_LOCK,
+ caller.getPackageName(),
+ getAffectedUser(parent)
+ ).getActiveAdmin();
} else {
admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
@@ -7482,7 +7514,7 @@
return;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(callerPackageName);
} else {
caller = getCallerIdentity();
@@ -7491,10 +7523,11 @@
boolean calledByProfileOwnerOnOrgOwnedDevice =
isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId());
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/*admin=*/ null,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
+ /*permission= */ MANAGE_DEVICE_POLICY_WIPE_DATA,
+ USES_POLICY_WIPE_DATA,
caller.getPackageName(),
factoryReset ? UserHandle.USER_ALL : getAffectedUser(calledOnParentInstance));
admin = enforcingAdmin.getActiveAdmin();
@@ -8524,7 +8557,7 @@
isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
Preconditions.checkCallAuthorization(isProfileOwner(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
}
}
@@ -8538,7 +8571,7 @@
targetUserId).getActiveAdmin();
} else {
ap = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
+ getProfileOwnerOrDefaultDeviceOwnerLocked(caller.getUserId()), parent);
}
if (ap.disableScreenCapture != disabled) {
@@ -8570,15 +8603,21 @@
}
if (admin != null && admin.disableScreenCapture) {
setScreenCaptureDisabled(UserHandle.USER_ALL);
- } else {
- // Otherwise, update screen capture only for the calling user.
- admin = getProfileOwnerAdminLocked(adminUserId);
- if (admin != null && admin.disableScreenCapture) {
- setScreenCaptureDisabled(adminUserId);
- } else {
- setScreenCaptureDisabled(UserHandle.USER_NULL);
- }
+ return;
}
+ // Otherwise, update screen capture only for the calling user.
+ admin = getProfileOwnerAdminLocked(adminUserId);
+ if (admin != null && admin.disableScreenCapture) {
+ setScreenCaptureDisabled(adminUserId);
+ return;
+ }
+ // If the admin is permission based, update only for the calling user.
+ admin = getUserData(adminUserId).createOrGetPermissionBasedAdmin(adminUserId);
+ if (admin != null && admin.disableScreenCapture) {
+ setScreenCaptureDisabled(adminUserId);
+ return;
+ }
+ setScreenCaptureDisabled(UserHandle.USER_NULL);
}
// Set the latest screen capture policy, overriding any existing ones.
@@ -8716,18 +8755,25 @@
synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
"Managed profile cannot set auto time required");
- ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- if (admin.requireAutoTime != required) {
- admin.requireAutoTime = required;
- saveSettingsLocked(caller.getUserId());
- requireAutoTimeChanged = true;
+
+ if (isPolicyEngineForFinanceFlagEnabled()) {
+ EnforcingAdmin admin = getEnforcingAdminForCaller(who, who.getPackageName());
+ setGlobalUserRestrictionInternal(
+ admin, UserManager.DISALLOW_CONFIG_DATE_TIME, required);
+ } else {
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+ if (admin.requireAutoTime != required) {
+ admin.requireAutoTime = required;
+ saveSettingsLocked(caller.getUserId());
+ requireAutoTimeChanged = true;
+ }
+ // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so
+ // propagate updated restrictions to the framework.
+ if (requireAutoTimeChanged) {
+ pushUserRestrictions(caller.getUserId());
+ }
}
}
- // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so propagate
- // updated restrictions to the framework.
- if (requireAutoTimeChanged) {
- pushUserRestrictions(caller.getUserId());
- }
// Turn AUTO_TIME on in settings if it is required
if (required) {
mInjector.binderWithCleanCallingIdentity(
@@ -8749,22 +8795,30 @@
if (!mHasFeature) {
return false;
}
- synchronized (getLockObject()) {
- ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner != null && deviceOwner.requireAutoTime) {
- // If the device owner enforces auto time, we don't need to check the PO's
- return true;
- }
-
- // Now check to see if any profile owner on any user enforces auto time
- for (Integer userId : mOwners.getProfileOwnerKeys()) {
- ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
- if (profileOwner != null && profileOwner.requireAutoTime) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
+ Boolean required = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(
+ UserManager.DISALLOW_CONFIG_DATE_TIME),
+ mInjector.binderGetCallingUserHandle().getIdentifier());
+ return required != null && required;
+ } else {
+ synchronized (getLockObject()) {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.requireAutoTime) {
+ // If the device owner enforces auto time, we don't need to check the PO's
return true;
}
- }
- return false;
+ // Now check to see if any profile owner on any user enforces auto time
+ for (Integer userId : mOwners.getProfileOwnerKeys()) {
+ ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+ if (profileOwner != null && profileOwner.requireAutoTime) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
}
@@ -8848,7 +8902,7 @@
caller = getCallerIdentity(who);
}
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
// The effect of this policy is device-wide.
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
@@ -9061,7 +9115,7 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -9070,14 +9124,15 @@
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED);
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_CAMERA,
caller.getPackageName(),
getProfileParentUserIfRequested(userId, parent));
- admin = enforcingAdmin.getActiveAdmin();
+
+ setBackwardCompatibleUserRestriction(
+ caller, enforcingAdmin, UserManager.DISALLOW_CAMERA, disabled, parent);
} else {
Objects.requireNonNull(who, "ComponentName is null");
if (parent) {
@@ -9085,22 +9140,19 @@
isProfileOwnerOfOrganizationOwnedDevice(caller));
}
synchronized (getLockObject()) {
- admin = getActiveAdminForCallerLocked(who,
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA, parent);
+ if (admin.disableCamera != disabled) {
+ admin.disableCamera = disabled;
+ saveSettingsLocked(userId);
+ }
}
+ // Tell the user manager that the restrictions have changed.
+ pushUserRestrictions(userId);
}
- synchronized (getLockObject()) {
- if (admin.disableCamera != disabled) {
- admin.disableCamera = disabled;
- saveSettingsLocked(userId);
- }
- }
- // Tell the user manager that the restrictions have changed.
- pushUserRestrictions(userId);
-
final int affectedUserId = parent ? getProfileParentId(userId) : userId;
- if (SecurityLog.isLoggingEnabled()) {
+ if (SecurityLog.isLoggingEnabled() && who != null) {
SecurityLog.writeEvent(SecurityLog.TAG_CAMERA_POLICY_SET,
who.getPackageName(), userId, affectedUserId, disabled ? 1 : 0);
}
@@ -9123,12 +9175,12 @@
return false;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
Preconditions.checkCallAuthorization(
hasFullCrossUsersPermission(caller, userHandle)
|| isCameraServerUid(caller)
@@ -9144,33 +9196,44 @@
}
}
- synchronized (getLockObject()) {
- if (who != null) {
- ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return (admin != null) && admin.disableCamera;
- }
- // First, see if DO has set it. If so, it's device-wide.
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner != null && deviceOwner.disableCamera) {
- return true;
- }
+ int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- // Return the strictest policy across all participating admins.
- List<ActiveAdmin> admins;
- final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- if (isPermissionCheckFlagEnabled()) {
- admins = getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
- affectedUserId);
+ if (isPolicyEngineForFinanceFlagEnabled()) {
+ PolicyDefinition<Boolean> policy =
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(
+ UserManager.DISALLOW_CAMERA);
+ if (who != null) {
+ EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName);
+ return Boolean.TRUE.equals(
+ mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ policy, admin, affectedUserId));
} else {
- admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
+ return Boolean.TRUE.equals(
+ mDevicePolicyEngine.getResolvedPolicy(policy, affectedUserId));
}
- // Determine whether or not the device camera is disabled for any active admins.
- for (ActiveAdmin activeAdmin : admins) {
- if (activeAdmin.disableCamera) {
+ } else {
+ synchronized (getLockObject()) {
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
+ return (admin != null) && admin.disableCamera;
+ }
+ // First, see if DO has set it. If so, it's device-wide.
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.disableCamera) {
return true;
}
+
+ // Return the strictest policy across all participating admins.
+ List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
+
+ // Determine whether or not the device camera is disabled for any active admins.
+ for (ActiveAdmin activeAdmin : admins) {
+ if (activeAdmin.disableCamera) {
+ return true;
+ }
+ }
+ return false;
}
- return false;
}
}
@@ -9192,7 +9255,7 @@
final int userHandle = caller.getUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
// SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9271,7 +9334,7 @@
synchronized (getLockObject()) {
if (who != null) {
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin admin = getEnforcingAdminForCaller(
who, who.getPackageName());
Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
@@ -9285,7 +9348,7 @@
}
}
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
Integer features = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
affectedUserId);
@@ -10319,8 +10382,10 @@
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ final String truncatedProfileName =
+ profileName.substring(0, Math.min(profileName.length(), MAX_PROFILE_NAME_LENGTH));
mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setUserName(caller.getUserId(), profileName);
+ mUserManager.setUserName(caller.getUserId(), truncatedProfileName);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
.setAdmin(caller.getComponentName())
@@ -11211,14 +11276,14 @@
public void addPersistentPreferredActivity(ComponentName who, String callerPackageName,
IntentFilter filter, ComponentName activity) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
final int userId = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin;
if (who == null) {
enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
@@ -11266,14 +11331,14 @@
public void clearPackagePersistentPreferredActivities(ComponentName who,
String callerPackageName, String packageName) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
final int userId = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin;
if (who == null) {
enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
@@ -11326,7 +11391,7 @@
for (PolicyKey key : keys) {
if (!(key instanceof IntentFilterPolicyKey)) {
throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not"
- + "of type PersistentPreferredActivityPolicyKey");
+ + "of type IntentFilterPolicyKey");
}
IntentFilterPolicyKey parsedKey =
(IntentFilterPolicyKey) key;
@@ -11413,7 +11478,7 @@
RoleManager.ROLE_DIALER, packageName, 0, UserHandle.of(callerUserId),
AsyncTask.THREAD_POOL_EXECUTOR, callback);
try {
- future.get(5, TimeUnit.SECONDS);
+ future.get(20, TimeUnit.SECONDS);
} catch (TimeoutException e) {
throw new IllegalArgumentException("Timeout when setting the app as the dialer", e);
} catch (ExecutionException e) {
@@ -11465,7 +11530,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
- if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -12461,7 +12526,7 @@
final int userId = user.id;
- if (isDevicePolicyEngineEnabled()) {
+ if (isPermissionCheckFlagEnabled()) {
mDevicePolicyEngine.handleUserCreated(user);
}
@@ -12830,7 +12895,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13059,7 +13124,7 @@
boolean parent) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackage);
} else {
caller = getCallerIdentity(who);
@@ -13071,42 +13136,37 @@
}
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
int affectedUserId = parent ? getProfileParentId(userId) : userId;
EnforcingAdmin admin = enforcePermissionForUserRestriction(
who,
key,
caller.getPackageName(),
affectedUserId);
- PolicyDefinition<Boolean> policyDefinition =
- PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
- if (enabledFromThisOwner) {
- mDevicePolicyEngine.setLocalPolicy(
- policyDefinition,
- admin,
- new BooleanPolicyValue(true),
- affectedUserId);
- } else {
- // Remove any local and global policy that was set by the admin
- if (!policyDefinition.isLocalOnlyPolicy()) {
- mDevicePolicyEngine.removeGlobalPolicy(
- policyDefinition,
- admin);
- }
- if (!policyDefinition.isGlobalOnlyPolicy()) {
- mDevicePolicyEngine.removeLocalPolicy(
- policyDefinition,
- admin,
- userId);
+ if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+ PolicyDefinition<Boolean> policyDefinition =
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
+ if (enabledFromThisOwner) {
+ setLocalUserRestrictionInternal(
+ admin, key, /* enabled= */ true, affectedUserId);
+ } else {
+ // Remove any local and global policy that was set by the admin
+ if (!policyDefinition.isLocalOnlyPolicy()) {
+ setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false);
+ }
+ if (!policyDefinition.isGlobalOnlyPolicy()) {
+ setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId);
- int parentUserId = getProfileParentId(userId);
- if (parentUserId != userId) {
- mDevicePolicyEngine.removeLocalPolicy(
- policyDefinition,
- admin,
- parentUserId);
+ int parentUserId = getProfileParentId(userId);
+ if (parentUserId != userId) {
+ setLocalUserRestrictionInternal(
+ admin, key, /* enabled= */ false, parentUserId);
+ }
}
}
+ } else {
+ setBackwardCompatibleUserRestriction(
+ caller, admin, key, enabledFromThisOwner, parent);
}
} else {
Objects.requireNonNull(who, "ComponentName is null");
@@ -13164,6 +13224,31 @@
logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
}
+ private void setBackwardCompatibleUserRestriction(
+ CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
+ boolean parent) {
+ synchronized (getLockObject()) {
+ if (isDeviceOwner(caller)) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId());
+ }
+ } else if (isProfileOwner(caller)) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
+ || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)
+ && UserRestrictionsUtils.isGlobal(
+ OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
+ }
+ }
+ }
+ }
+
@Override
public void setUserRestrictionGlobally(String callerPackage, String key) {
final CallerIdentity caller = getCallerIdentity(callerPackage);
@@ -13171,27 +13256,63 @@
return;
}
- int userHandle = caller.getUserId();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
- if (!useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
- throw new IllegalStateException("One or more admins are not targeting Android 14.");
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ throw new IllegalStateException("Feature flag is not enabled.");
}
+
+ if (!mInjector.isChangeEnabled(
+ ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+ throw new IllegalStateException("Calling package is not targeting Android U.");
+ }
+
EnforcingAdmin admin = enforcePermissionForUserRestriction(
- /*who=*/ null,
+ /* who= */ null,
key,
caller.getPackageName(),
caller.getUserId()
);
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.getPolicyDefinitionForUserRestriction(key),
- admin,
- new BooleanPolicyValue(true));
+ setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
}
+ private void setLocalUserRestrictionInternal(
+ EnforcingAdmin admin, String key, boolean enabled, int userId) {
+ PolicyDefinition<Boolean> policyDefinition =
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
+ if (enabled) {
+ mDevicePolicyEngine.setLocalPolicy(
+ policyDefinition,
+ admin,
+ new BooleanPolicyValue(true),
+ userId);
+ } else {
+ mDevicePolicyEngine.removeLocalPolicy(
+ policyDefinition,
+ admin,
+ userId);
+ }
+ }
+
+ private void setGlobalUserRestrictionInternal(
+ EnforcingAdmin admin, String key, boolean enabled) {
+ PolicyDefinition<Boolean> policyDefinition =
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
+ if (enabled) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.getPolicyDefinitionForUserRestriction(key),
+ admin,
+ new BooleanPolicyValue(true));
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ policyDefinition,
+ admin);
+ }
+ }
+
private void logUserRestrictionCall(
String key, boolean enabled, boolean parent, CallerIdentity caller) {
final int eventId = enabled
@@ -13211,7 +13332,7 @@
}
private void saveUserRestrictionsLocked(int userId) {
- if (isDevicePolicyEngineEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
// User restrictions are handled in the policy engine
return;
}
@@ -13229,6 +13350,10 @@
* will be the target user id.
*/
private void pushUserRestrictions(int originatingUserId) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
+ // User restrictions are handled in the policy engine
+ return;
+ }
final Bundle global;
final RestrictionsSet local = new RestrictionsSet();
final boolean isDeviceOwner;
@@ -13281,17 +13406,24 @@
return null;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackage);
} else {
caller = getCallerIdentity(who);
}
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
+ int targetUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
- return getUserRestrictionsFromPolicyEngine(
- admin,
- parent ? getProfileParentId(caller.getUserId()) : caller.getUserId());
+ Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
+ // Add global restrictions set by the admin as well if admin is not targeting Android U.
+ if (!mInjector.isChangeEnabled(
+ ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+ restrictions.putAll(
+ getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL));
+ }
+ return restrictions;
} else {
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
@@ -13475,8 +13607,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (!useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
- throw new IllegalStateException("One or more admins are not targeting Android 14.");
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ throw new IllegalStateException("Feature flag is not enabled.");
}
EnforcingAdmin admin = getEnforcingAdminForCaller(/*who=*/ null, caller.getPackageName());
@@ -13544,8 +13676,31 @@
Slogf.v(LOG_TAG, "calling pm.setApplicationHiddenSettingAsUser(%s, %b, %d)",
packageName, hidden, userId);
}
- result = mInjector.binderWithCleanCallingIdentity(() -> mIPackageManager
- .setApplicationHiddenSettingAsUser(packageName, hidden, userId));
+ if (isPermissionCheckFlagEnabled()) {
+ EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.APPLICATION_HIDDEN(packageName),
+ admin,
+ new BooleanPolicyValue(hidden),
+ userId);
+ Boolean resolvedPolicy = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.APPLICATION_HIDDEN(packageName), userId);
+ result = mInjector.binderWithCleanCallingIdentity(() -> {
+ try {
+ // This is a best effort to continue returning the same value that was
+ // returned before the policy engine migration.
+ return mInjector.getIPackageManager().getPackageInfo(
+ packageName, MATCH_UNINSTALLED_PACKAGES, userId) != null
+ && (mIPackageManager.getApplicationHiddenSettingAsUser(
+ packageName, userId) == hidden);
+ } catch (RemoteException e) {
+ return false;
+ }
+ });
+ } else {
+ result = mInjector.binderWithCleanCallingIdentity(() -> mIPackageManager
+ .setApplicationHiddenSettingAsUser(packageName, hidden, userId));
+ }
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_APPLICATION_HIDDEN)
@@ -13861,7 +14016,7 @@
boolean uninstallBlocked) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (useDevicePolicyEngine(caller, DELEGATION_BLOCK_UNINSTALL)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
@@ -14415,14 +14570,14 @@
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin;
synchronized (getLockObject()) {
enforcingAdmin = enforceCanCallLockTaskLocked(who, callerPackageName);
@@ -14473,14 +14628,14 @@
@Override
public String[] getLockTaskPackages(ComponentName who, String callerPackageName) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
final int userHandle = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
synchronized (getLockObject()) {
enforceCanQueryLockTaskLocked(who, caller.getPackageName());
}
@@ -14513,8 +14668,7 @@
}
final int userId = mInjector.userHandleGetCallingUserId();
- // Is it ok to just check that no active policies exist currently?
- if (isDevicePolicyEngineFlagEnabled() && mDevicePolicyEngine.hasActivePolicies()) {
+ if (isPermissionCheckFlagEnabled()) {
LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.LOCK_TASK, userId);
if (policy == null) {
@@ -14540,7 +14694,7 @@
"Cannot use LOCK_TASK_FEATURE_NOTIFICATIONS without LOCK_TASK_FEATURE_HOME");
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -14550,7 +14704,7 @@
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
}
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin;
synchronized (getLockObject()) {
enforcingAdmin = enforceCanCallLockTaskLocked(who,
@@ -14593,14 +14747,14 @@
@Override
public int getLockTaskFeatures(ComponentName who, String callerPackageName) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
final int userHandle = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
synchronized (getLockObject()) {
enforceCanQueryLockTaskLocked(who, caller.getPackageName());
}
@@ -16360,12 +16514,31 @@
enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
}
}
- if (useDevicePolicyEngine(caller, DELEGATION_PERMISSION_GRANT)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
callerPackage,
caller.getUserId());
+ if (SENSOR_PERMISSIONS.contains(permission)
+ && grantState == PERMISSION_GRANT_STATE_GRANTED
+ && (!canAdminGrantSensorsPermissions() || isCallerDelegate(caller))) {
+ if (mInjector.isChangeEnabled(THROW_SECURITY_EXCEPTION_FOR_SENSOR_PERMISSIONS,
+ caller.getPackageName(), caller.getUserId())) {
+ throw new SecurityException(
+ "Caller not permitted to grant sensor permissions.");
+ } else {
+ // This is to match the legacy behaviour.
+ callback.sendResult(Bundle.EMPTY);
+ return;
+ }
+ }
+ // Check all the states where Exceptions aren't thrown but the permission
+ // isn't granted either.
+ if (!canGrantPermission(caller, permission, packageName)) {
+ callback.sendResult(null);
+ return;
+ }
// TODO(b/266924257): decide how to handle the internal state if the package doesn't
// exist, or the permission isn't requested by the app, because we could end up with
// inconsistent state between the policy engine and package manager. Also a package
@@ -16441,6 +16614,41 @@
}
}
+ private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
+ {
+ SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+ SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+ SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA);
+ SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
+ SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
+ SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
+ SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
+ SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ SENSOR_PERMISSIONS.add(
+ Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
+ SENSOR_PERMISSIONS.add(
+ Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
+ }
+
+ private boolean canGrantPermission(CallerIdentity caller, String permission,
+ String targetPackageName) {
+ boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
+ >= android.os.Build.VERSION_CODES.Q;
+ if (!isPostQAdmin) {
+ // Legacy admins assume that they cannot control pre-M apps
+ if (getTargetSdk(targetPackageName, caller.getUserId())
+ < android.os.Build.VERSION_CODES.M) {
+ return false;
+ }
+ }
+ if (!isRuntimePermission(permission)) {
+ return false;
+ }
+ return true;
+ }
+
private void enforcePermissionGrantStateOnFinancedDevice(
String packageName, String permission) {
if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
@@ -17537,7 +17745,6 @@
synchronized (getLockObject()) {
if (isPermissionCheckFlagEnabled()) {
- // TODO: add support for DELEGATION_SECURITY_LOGGING
enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
@@ -17618,7 +17825,8 @@
final CallerIdentity caller = getCallerIdentity(admin, packageName);
if (isPermissionCheckFlagEnabled()) {
- // TODO: Restore the "affiliated users" check
+ Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked());
enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
@@ -17670,7 +17878,9 @@
final CallerIdentity caller = getCallerIdentity(admin, packageName);
if (isPermissionCheckFlagEnabled()) {
- // TODO: Restore the "affiliated users" check
+ Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked());
+
enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
@@ -17865,11 +18075,6 @@
// The removed admin might have disabled camera, so update user
// restrictions.
pushUserRestrictions(userHandle);
-
- // The removed admin might've been stopping the migration if it was targeting pre Android U
- if (shouldMigrateToDevicePolicyEngine()) {
- migratePoliciesToDevicePolicyEngine();
- }
}
@Override
@@ -18547,7 +18752,7 @@
}
final int userId = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -18611,7 +18816,7 @@
final int userId = caller.getUserId();
boolean result = false;
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -18657,7 +18862,7 @@
}
int userId = caller.getUserId();
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -18709,7 +18914,7 @@
boolean result = false;
final String password = passwordOrNull != null ? passwordOrNull : "";
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPermissionCheckFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19898,7 +20103,7 @@
List<String> packages) {
Objects.requireNonNull(packages, "packages is null");
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -19906,7 +20111,7 @@
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
@@ -19980,13 +20185,13 @@
public List<String> getUserControlDisabledPackages(ComponentName who,
String callerPackageName) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
enforceCanQuery(
MANAGE_DEVICE_POLICY_APPS_CONTROL,
caller.getPackageName(),
@@ -22069,7 +22274,7 @@
}
private void handleFinancedDeviceKioskRoleChange() {
- if (!isDevicePolicyEngineEnabled()) {
+ if (!isPermissionCheckFlagEnabled()) {
return;
}
Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED);
@@ -22226,244 +22431,208 @@
// Permissions of existing DPC types.
private static final List<String> DEFAULT_DEVICE_OWNER_PERMISSIONS = List.of(
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
+ MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
- SET_TIME,
- SET_TIME_ZONE,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_WIFI,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_SYSTEM_UPDATES,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
- MANAGE_DEVICE_POLICY_MTE,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_CERTIFICATES,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
- MANAGE_DEVICE_POLICY_CAMERA,
- MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
- MANAGE_DEVICE_POLICY_DEFAULT_SMS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_STATUS_BAR,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
MANAGE_DEVICE_POLICY_BLUETOOTH,
MANAGE_DEVICE_POLICY_CALLS,
MANAGE_DEVICE_POLICY_CAMERA,
+ MANAGE_DEVICE_POLICY_CERTIFICATES,
+ MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
+ MANAGE_DEVICE_POLICY_DEFAULT_SMS,
MANAGE_DEVICE_POLICY_DISPLAY,
MANAGE_DEVICE_POLICY_FACTORY_RESET,
MANAGE_DEVICE_POLICY_FUN,
MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
+ MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_LOCALE,
MANAGE_DEVICE_POLICY_LOCATION,
+ MANAGE_DEVICE_POLICY_LOCK,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+ MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MTE,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
+ MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ MANAGE_DEVICE_POLICY_PACKAGE_STATE,
MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
MANAGE_DEVICE_POLICY_PRINTING,
- MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS,
MANAGE_DEVICE_POLICY_PROFILES,
MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
+ MANAGE_DEVICE_POLICY_RESET_PASSWORD,
+ MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS,
+ MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
MANAGE_DEVICE_POLICY_SMS,
+ MANAGE_DEVICE_POLICY_STATUS_BAR,
+ MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
+ MANAGE_DEVICE_POLICY_SYSTEM_UPDATES,
MANAGE_DEVICE_POLICY_TIME,
+ MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_WALLPAPER,
MANAGE_DEVICE_POLICY_WIFI,
MANAGE_DEVICE_POLICY_WINDOWS,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS
+ MANAGE_DEVICE_POLICY_WIPE_DATA,
+ SET_TIME,
+ SET_TIME_ZONE
);
private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of(
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_CALLS,
MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
+ MANAGE_DEVICE_POLICY_FACTORY_RESET,
MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_USERS,
+ MANAGE_DEVICE_POLICY_KEYGUARD,
+ MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
- private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
+ MANAGE_DEVICE_POLICY_USERS,
+ MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
+ );
+
+ /**
+ * All the permisisons granted to a profile owner.
+ */
+ private static final List<String> PROFILE_OWNER_PERMISSIONS =
List.of(
- MANAGE_DEVICE_POLICY_ACROSS_USERS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
- SET_TIME,
- SET_TIME_ZONE,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_WIFI,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_SYSTEM_UPDATES,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
- MANAGE_DEVICE_POLICY_MTE,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
- MANAGE_DEVICE_POLICY_BLUETOOTH,
MANAGE_DEVICE_POLICY_CALLS,
- MANAGE_DEVICE_POLICY_CAMERA,
MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
MANAGE_DEVICE_POLICY_DISPLAY,
MANAGE_DEVICE_POLICY_FACTORY_RESET,
MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
+ MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_LOCALE,
MANAGE_DEVICE_POLICY_LOCATION,
+ MANAGE_DEVICE_POLICY_LOCK,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_MICROPHONE,
- MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
- MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
+ MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ MANAGE_DEVICE_POLICY_PACKAGE_STATE,
MANAGE_DEVICE_POLICY_PRINTING,
MANAGE_DEVICE_POLICY_PROFILES,
MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS,
- MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_RESET_PASSWORD,
+ MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
+ MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
- MANAGE_DEVICE_POLICY_SMS,
+ MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
MANAGE_DEVICE_POLICY_TIME,
MANAGE_DEVICE_POLICY_VPN,
- MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
- MANAGE_DEVICE_POLICY_DEFAULT_SMS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_WIFI,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_SYSTEM_UPDATES,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
- MANAGE_DEVICE_POLICY_MTE,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_CERTIFICATES);
- private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of(
- SET_TIME,
- SET_TIME_ZONE,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
- MANAGE_DEVICE_POLICY_BLUETOOTH,
- MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_FUN,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
- MANAGE_DEVICE_POLICY_USERS,
- MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
- MANAGE_DEVICE_POLICY_SAFE_BOOT,
- MANAGE_DEVICE_POLICY_SMS,
- MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_WINDOWS,
- MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
- MANAGE_DEVICE_POLICY_CAMERA,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_STATUS_BAR,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
- private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of(
- MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
- MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
- MANAGE_DEVICE_POLICY_AUTOFILL,
- MANAGE_DEVICE_POLICY_CALLS,
- MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
- MANAGE_DEVICE_POLICY_DISPLAY,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_LOCALE,
- MANAGE_DEVICE_POLICY_LOCATION,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
- MANAGE_DEVICE_POLICY_PRINTING,
- MANAGE_DEVICE_POLICY_PROFILES,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
- MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
- MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_VPN,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS
+ MANAGE_DEVICE_POLICY_WIPE_DATA
);
+ /**
+ * All the additional permissions granted to an organisation owned profile owner.
+ */
+ private static final List<String>
+ ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
+ List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_BLUETOOTH,
+ MANAGE_DEVICE_POLICY_CAMERA,
+ MANAGE_DEVICE_POLICY_CERTIFICATES,
+ MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
+ MANAGE_DEVICE_POLICY_DEFAULT_SMS,
+ MANAGE_DEVICE_POLICY_LOCALE,
+ MANAGE_DEVICE_POLICY_MICROPHONE,
+ MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MTE,
+ MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
+ MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
+ MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS,
+ MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ MANAGE_DEVICE_POLICY_SMS,
+ MANAGE_DEVICE_POLICY_SYSTEM_UPDATES,
+ MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
+ MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
+ MANAGE_DEVICE_POLICY_WIFI,
+ SET_TIME,
+ SET_TIME_ZONE
+ );
+
+
+ private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS =
+ List.of(
+ MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
+ MANAGE_DEVICE_POLICY_BLUETOOTH,
+ MANAGE_DEVICE_POLICY_CAMERA,
+ MANAGE_DEVICE_POLICY_DISPLAY,
+ MANAGE_DEVICE_POLICY_FUN,
+ MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
+ MANAGE_DEVICE_POLICY_PRINTING,
+ MANAGE_DEVICE_POLICY_PROFILES,
+ MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
+ MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_SMS,
+ MANAGE_DEVICE_POLICY_STATUS_BAR,
+ MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
+ MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
+ MANAGE_DEVICE_POLICY_USERS,
+ MANAGE_DEVICE_POLICY_WINDOWS,
+ SET_TIME,
+ SET_TIME_ZONE
+ );
+
+ /**
+ * Combination of {@link PROFILE_OWNER_PERMISSIONS} and
+ * {@link ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS}.
+ */
+ private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
+ new ArrayList();
+
+ /**
+ * Combination of {@link PROFILE_OWNER_PERMISSIONS} and
+ * {@link ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS}.
+ */
+ private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS =
+ new ArrayList();
+
+
private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>();
{
+ // Organisation owned profile owners have all the permission of a profile owner plus
+ // some extra permissions.
+ PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS.addAll(PROFILE_OWNER_PERMISSIONS);
+ PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS.addAll(
+ ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS);
+ // Profile owners on user 0 have all the permission of a profile owner plus
+ // some extra permissions.
+ PROFILE_OWNER_ON_USER_0_PERMISSIONS.addAll(PROFILE_OWNER_PERMISSIONS);
+ PROFILE_OWNER_ON_USER_0_PERMISSIONS.addAll(ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS);
+
DPC_PERMISSIONS.put(DEFAULT_DEVICE_OWNER, DEFAULT_DEVICE_OWNER_PERMISSIONS);
DPC_PERMISSIONS.put(FINANCED_DEVICE_OWNER, FINANCED_DEVICE_OWNER_PERMISSIONS);
DPC_PERMISSIONS.put(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE,
@@ -22471,14 +22640,6 @@
DPC_PERMISSIONS.put(PROFILE_OWNER_ON_USER_0, PROFILE_OWNER_ON_USER_0_PERMISSIONS);
DPC_PERMISSIONS.put(PROFILE_OWNER, PROFILE_OWNER_PERMISSIONS);
}
-
- // Map of permission Active admin DEVICE_POLICY.
- //TODO(b/254253251) Fill this map in as new permissions are added for policies.
- private static final HashMap<String, Integer> ACTIVE_ADMIN_POLICIES = new HashMap<>();
- {
- //Any ActiveAdmin is able to call the support message APIs without certain policies.
- ACTIVE_ADMIN_POLICIES.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, null);
- }
//Map of Permission to Delegate Scope.
private static final HashMap<String, String> DELEGATE_SCOPES = new HashMap<>();
{
@@ -22492,75 +22653,40 @@
private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
new HashMap<>();
{
- // Time and Timezone is intrinsically global so there is no cross-user permission.
+ // The permissions are all intrinsically global and therefore have no cross-user permission.
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FACTORY_RESET, null);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MTE, null);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, null);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_STATUS_BAR, null);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, null);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, null);
CROSS_USER_PERMISSIONS.put(SET_TIME, null);
CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null);
- // system updates are intrinsically global so there is no cross-user permission
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, null);
- // security logs are intrinsically global so there is no cross-user permission
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, null);
- // usb signalling is intrinsically global so there is no cross-user permission
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, null);
- // mte is intrinsically global so there is no cross-user permission
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MTE, null);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FACTORY_RESET, null);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_STATUS_BAR, null);
- // Organisation identity policy will involve data of other organisations on the device and
- // therefore the FULL cross-user permission is required.
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
+
+ // The permissions are all critical for securing data within the current user and
+ // therefore are protected with MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL for
+ // cross-user calls.
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_KEYGUARD,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(
- MANAGE_DEVICE_POLICY_LOCK, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(
- MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
- // Granting runtime permissions can grant applications significant powers therefore the FULL
- // cross-user permission is required.
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+
+ // These permissions are required for securing device ownership without accessing user data
+ // and therefore are protected with MANAGE_DEVICE_POLICY_ACROSS_USERS for cross-user calls.
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLUETOOTH,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CALLS,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DISPLAY,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FUN,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCALE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
@@ -22569,46 +22695,77 @@
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PRINTING,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SMS,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SAFE_BOOT,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_TIME,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
+
+ // These permissions may grant access to user data and therefore must be protected with
+ // MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DISPLAY,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FUN,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCALE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PRINTING,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESET_PASSWORD,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WINDOWS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_ACROSS_USERS);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
}
/**
@@ -22632,6 +22789,26 @@
}
/**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ * The given permission will be checked along with its associated cross-user permission if it
+ * exists and the target user is different to the calling user.
+ * Returns an {@link EnforcingAdmin} for the caller.
+ *
+ * @param admin the component name of the admin.
+ * @param callerPackageName The package name of the calling application.
+ * @param permission The name of the permission being checked.
+ * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
+ String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) {
+ enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId);
+ return getEnforcingAdminForCaller(admin, callerPackageName);
+ }
+
+ /**
* Checks whether the calling process has been granted permission to query a device policy on
* a specific user.
* The given permission will be checked along with its associated cross-user permission if it
@@ -22653,6 +22830,9 @@
POLICY_IDENTIFIER_TO_PERMISSION.put(AUTO_TIMEZONE_POLICY, SET_TIME_ZONE);
}
+ private static final HashMap<String, Integer> POLICY_IDENTIFIER_TO_ACTIVE_ADMIN_POLICY =
+ new HashMap<>();
+
/**
* Checks if the calling process has been granted permission to apply a device policy on a
* specific user.
@@ -22668,6 +22848,36 @@
private void enforcePermission(String permission, String callerPackageName, int targetUserId)
throws SecurityException {
if (!hasPermission(permission, callerPackageName, targetUserId)) {
+ // TODO(b/276920002): Split the error messages so that the cross-user permission
+ // is only mentioned when it is needed.
+ throw new SecurityException("Caller does not have the required permissions for "
+ + "this user. Permissions required: {"
+ + permission
+ + ", "
+ + CROSS_USER_PERMISSIONS.get(permission)
+ + "(if calling cross-user)"
+ + "}");
+ }
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ * The given permission will be checked along with its associated cross-user permission if it
+ * exists and the target user is different to the calling user.
+ *
+ * @param callerPackageName The package name of the calling application.
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermission(String permission, int adminPolicy,
+ String callerPackageName, int targetUserId)
+ throws SecurityException {
+ if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
+ // TODO(b/276920002): Split the error messages so that the cross-user permission
+ // is only mentioned when it is needed.
throw new SecurityException("Caller does not have the required permissions for "
+ "this user. Permissions required: {"
+ permission
@@ -22709,16 +22919,26 @@
*/
private boolean hasPermission(String permission, String callerPackageName, int targetUserId) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
- boolean hasPermissionOnOwnUser = hasPermission(permission, callerPackageName);
+ boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
boolean hasPermissionOnTargetUser = true;
if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
- callerPackageName);
+ caller.getPackageName());
}
return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
}
+ private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
+ int adminPolicy, int targetUserId) {
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
+ return true;
+ }
+ ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+ return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+ }
+
/**
* Return whether the calling process has been granted the given permission.
*
@@ -22761,23 +22981,6 @@
if (DELEGATE_SCOPES.containsKey(permission)) {
return isCallerDelegate(caller, DELEGATE_SCOPES.get(permission));
}
- // Check if the caller is an active admin that uses a certain policy.
- if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
- try {
- if (ACTIVE_ADMIN_POLICIES.get(permission) != null) {
- return getActiveAdminForCallerLocked(
- null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null;
- } else {
- // If the permission maps to no policy (null) this means that any active admin
- // has permission.
- return isCallerActiveAdminOrDelegate(caller, null);
- }
- } catch (SecurityException e) {
- // A security exception means there is not an active admin with permission and
- // therefore
- return false;
- }
- }
return false;
}
@@ -22830,9 +23033,7 @@
if (admin != null) {
return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
}
- if (admin == null) {
- admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- }
+ admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
}
@@ -22848,6 +23049,13 @@
DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
}
+ private boolean isPolicyEngineForFinanceFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG,
+ DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG);
+ }
+
private static boolean isKeepProfilesRunningFlagEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_DEVICE_POLICY_MANAGER,
@@ -23186,36 +23394,9 @@
});
}
- // TODO(b/266808047): handle DeviceAdmin migration when there is no DPCs on the device
private boolean shouldMigrateToDevicePolicyEngine() {
- return mInjector.binderWithCleanCallingIdentity(() -> {
- if (!isDevicePolicyEngineFlagEnabled()) {
- return false;
- }
- if (mOwners.isMigratedToPolicyEngine()) {
- return false;
- }
- // We're only checking if existing DPCs are not targeting U, regardless of what
- // DeviceAdmins are targeting, as they can access very limited APIs, and we'll ensure
- // that these APIs maintain the current behaviour of strictest applies.
- boolean hasDPCs = false;
- for (UserInfo userInfo : mUserManager.getUsers()) {
- List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
- if (activeAdmins == null) {
- continue;
- }
- for (ComponentName admin : activeAdmins) {
- if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id))) {
- if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE,
- admin.getPackageName(), userInfo.id)) {
- return false;
- }
- hasDPCs = true;
- }
- }
- }
- return hasDPCs;
- });
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ isPermissionCheckFlagEnabled() && !mOwners.isMigratedToPolicyEngine());
}
/**
@@ -23401,15 +23582,13 @@
// We need to add a mapping of policyId to permission in POLICY_IDENTIFIER_TO_PERMISSION
// for each migrated permission.
private List<ActiveAdmin> getNonDPCActiveAdminsForPolicyLocked(String policyIdentifier) {
- String permission = POLICY_IDENTIFIER_TO_PERMISSION.get(policyIdentifier);
- if (permission == null) {
- Slogf.e(LOG_TAG, "Can't find a permission for %s in POLICY_IDENTIFIER_TO_PERMISSION",
+ Integer activeAdminPolicy = POLICY_IDENTIFIER_TO_ACTIVE_ADMIN_POLICY.get(policyIdentifier);
+ if (activeAdminPolicy == null) {
+ Slogf.e(LOG_TAG,
+ "Can't find a active admin policy for %s in POLICY_IDENTIFIER_TO_PERMISSION",
policyIdentifier);
return new ArrayList<>();
}
- if (!ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
- return new ArrayList<>();
- }
List<ActiveAdmin> admins = new ArrayList<>();
for (UserInfo userInfo : mUserManager.getUsers()) {
@@ -23420,7 +23599,7 @@
}
DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
if (isActiveAdminWithPolicyForUserLocked(
- policy.mAdminMap.get(admin), ACTIVE_ADMIN_POLICIES.get(permission),
+ policy.mAdminMap.get(admin), activeAdminPolicy,
userInfo.id)) {
admins.add(policy.mAdminMap.get(admin));
}
@@ -23429,40 +23608,6 @@
return admins;
}
- private boolean useDevicePolicyEngine(CallerIdentity caller, @Nullable String delegateScope) {
- return isDevicePolicyEngineEnabled();
- }
-
- private boolean isDevicePolicyEngineEnabled() {
- return isDevicePolicyEngineFlagEnabled() && isPermissionCheckFlagEnabled();
- }
-
- private boolean isDevicePolicyEngineFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- ENABLE_DEVICE_POLICY_ENGINE_FLAG,
- DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG);
- }
-
- private boolean hasDPCsNotSupportingCoexistence() {
- return mInjector.binderWithCleanCallingIdentity(() -> {
- for (UserInfo userInfo : mUserManager.getUsers()) {
- List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
- if (activeAdmins == null) {
- continue;
- }
- for (ComponentName admin : activeAdmins) {
- if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id))
- && !mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE,
- admin.getPackageName(), userInfo.id)) {
- return true;
- }
- }
- }
- return false;
- });
- }
-
// TODO: this can actually accept an EnforcingAdmin that gets created in the permission
// check method.
private boolean isCallerActiveAdminOrDelegate(
@@ -23500,25 +23645,6 @@
}
}
- // TODO(b/266808047): This will return false for DeviceAdmins not targetting U, which is
- // inconsistent with the migration logic that allows migration with old DeviceAdmins.
- private boolean canAddActiveAdminIfPolicyEngineEnabled(String packageName, int userId) {
- if (!isDevicePolicyEngineFlagEnabled()) {
- return true;
- }
- if (hasDPCsNotSupportingCoexistence()) {
- return true;
- }
- if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, packageName, userId)) {
- // This will always return true unless we turn off coexistence, in which case it will
- // return true if no current admins exist, or more than one admin exist
- return mDevicePolicyEngine.canAdminAddPolicies(packageName, userId);
- }
- // Is it ok to just check that no active policies exist currently, or should we return false
- // if the policy engine was ever used?
- return !mDevicePolicyEngine.hasActivePolicies();
- }
-
@Override
public boolean isDeviceFinanced(String callerPackageName) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index bff6d32..45a2d2a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -32,21 +32,25 @@
final class IntegerPolicySerializer extends PolicySerializer<Integer> {
+ private static final String TAG = "IntegerPolicySerializer";
+
+ private static final String ATTR_VALUE = "value";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Integer value) throws IOException {
Objects.requireNonNull(value);
- serializer.attributeInt(/* namespace= */ null, attributeName, value);
+ serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ IntegerPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new IntegerPolicyValue(
- parser.getAttributeInt(/* namespace= */ null, attributeName));
+ parser.getAttributeInt(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+ Log.e(TAG, "Error parsing Integer policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 3265b61..0f6f3c5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -32,12 +32,14 @@
final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
- private static final String ATTR_PACKAGES = ":packages";
+ private static final String TAG = "LockTaskPolicySerializer";
+
+ private static final String ATTR_PACKAGES = "packages";
private static final String ATTR_PACKAGES_SEPARATOR = ";";
- private static final String ATTR_FLAGS = ":flags";
+ private static final String ATTR_FLAGS = "flags";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull LockTaskPolicy value) throws IOException {
Objects.requireNonNull(value);
if (value.getPackages() == null || value.getPackages().isEmpty()) {
@@ -46,31 +48,31 @@
}
serializer.attribute(
/* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES,
+ ATTR_PACKAGES,
String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages()));
serializer.attributeInt(
/* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS,
+ ATTR_FLAGS,
value.getFlags());
}
@Override
- LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+ LockTaskPolicy readFromXml(TypedXmlPullParser parser) {
String packagesStr = parser.getAttributeValue(
/* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES);
+ ATTR_PACKAGES);
if (packagesStr == null) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+ Log.e(TAG, "Error parsing LockTask policy value.");
return null;
}
Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
try {
int flags = parser.getAttributeInt(
/* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS);
+ ATTR_FLAGS);
return new LockTaskPolicy(packages, flags);
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+ Log.e(TAG, "Error parsing LockTask policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
index f77d051..522c4b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
@@ -32,21 +32,25 @@
final class LongPolicySerializer extends PolicySerializer<Long> {
+ private static final String TAG = "LongPolicySerializer";
+
+ private static final String ATTR_VALUE = "value";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Long value) throws IOException {
Objects.requireNonNull(value);
- serializer.attributeLong(/* namespace= */ null, attributeName, value);
+ serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- LongPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ LongPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new LongPolicyValue(
- parser.getAttributeLong(/* namespace= */ null, attributeName));
+ parser.getAttributeLong(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Long policy value", e);
+ Log.e(TAG, "Error parsing Long policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8812c3d..509a66b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -226,8 +226,7 @@
* Passing in {@code null} for {@code packageName} will return
* {@link #GENERIC_APPLICATION_RESTRICTIONS}.
*/
- static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(
- String packageName) {
+ static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(String packageName) {
if (packageName == null) {
return GENERIC_APPLICATION_RESTRICTIONS;
}
@@ -254,6 +253,34 @@
(Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
new IntegerPolicySerializer());
+ // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+ // actual policy with the correct arguments (i.e. packageName) when reading the policies from
+ // xml.
+ static PolicyDefinition<Boolean> GENERIC_APPLICATION_HIDDEN =
+ new PolicyDefinition<>(
+ new PackagePolicyKey(
+ DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY),
+ // TODO(b/276713779): Don't need to take in a resolution mechanism since its
+ // never used, but might need some refactoring to not always assume a non-null
+ // mechanism.
+ TRUE_MORE_RESTRICTIVE,
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::setApplicationHidden,
+ new BooleanPolicySerializer());
+
+ /**
+ * Passing in {@code null} for {@code packageName} will return
+ * {@link #GENERIC_APPLICATION_HIDDEN}.
+ */
+ static PolicyDefinition<Boolean> APPLICATION_HIDDEN(String packageName) {
+ if (packageName == null) {
+ return GENERIC_APPLICATION_HIDDEN;
+ }
+ return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition(
+ new PackagePolicyKey(
+ DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName));
+ }
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
@@ -272,6 +299,10 @@
GENERIC_APPLICATION_RESTRICTIONS);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.RESET_PASSWORD_TOKEN_POLICY,
RESET_PASSWORD_TOKEN);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.KEYGUARD_DISABLED_FEATURES_POLICY,
+ KEYGUARD_DISABLED_FEATURES);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY,
+ GENERIC_APPLICATION_HIDDEN);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -500,7 +531,6 @@
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- // TODO: here and elsewhere, add tags to ensure attributes aren't overridden by duplication.
mPolicyKey.saveToXml(serializer);
}
@@ -523,14 +553,14 @@
return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
}
- void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ void savePolicyValueToXml(TypedXmlSerializer serializer, V value)
throws IOException {
- mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value);
+ mPolicySerializer.saveToXml(mPolicyKey, serializer, value);
}
@Nullable
- PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
- return mPolicySerializer.readFromXml(parser, attributeName);
+ PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser) {
+ return mPolicySerializer.readFromXml(parser);
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index fd91249..d65d366 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -73,7 +73,7 @@
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
- + "PermissionGrantStatePolicyKey");
+ + "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
}
PackagePermissionPolicyKey parsedKey = (PackagePermissionPolicyKey) policyKey;
Objects.requireNonNull(parsedKey.getPermissionName());
@@ -165,7 +165,7 @@
try {
if (!(policyKey instanceof IntentFilterPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
- + "IntentFilterPolicyKey");
+ + "IntentFilterPolicyKey, passed in policyKey is: " + policyKey);
}
IntentFilterPolicyKey parsedKey =
(IntentFilterPolicyKey) policyKey;
@@ -193,7 +193,7 @@
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
- + "PackagePolicyKey");
+ + "PackagePolicyKey, passed in policyKey is: " + policyKey);
}
PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
String packageName = Objects.requireNonNull(parsedKey.getPackageName());
@@ -211,7 +211,7 @@
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof UserRestrictionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
- + "UserRestrictionPolicyKey");
+ + "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
}
UserRestrictionPolicyKey parsedKey =
(UserRestrictionPolicyKey) policyKey;
@@ -221,4 +221,20 @@
return true;
}));
}
+
+ static boolean setApplicationHidden(
+ @Nullable Boolean hide, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ if (!(policyKey instanceof PackagePolicyKey)) {
+ throw new IllegalArgumentException("policyKey is not of type "
+ + "PackagePolicyKey, passed in policyKey is: " + policyKey);
+ }
+ PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
+ String packageName = Objects.requireNonNull(parsedKey.getPackageName());
+ IPackageManager packageManager = AppGlobals.getPackageManager();
+ return packageManager.setApplicationHiddenSettingAsUser(
+ packageName, hide != null && hide, userId);
+ }));
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index 0ef431f..5af2fa2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -26,8 +26,7 @@
import java.io.IOException;
abstract class PolicySerializer<V> {
- abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
- String attributeName, @NonNull V value)
+ abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value)
throws IOException;
- abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName);
+ abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 3a792d8..741f209 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -35,11 +35,14 @@
* Class containing all values set for a certain policy by different admins.
*/
final class PolicyState<V> {
- private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
- private static final String ATTR_POLICY_VALUE = "policy-value";
- private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
+ private static final String TAG = "PolicyState";
+ private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
+
+ private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
+ private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
+ private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
+ private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
private final PolicyDefinition<V> mPolicyDefinition;
private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
new LinkedHashMap<>();
@@ -193,18 +196,24 @@
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
mPolicyDefinition.saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
if (mCurrentResolvedPolicy != null) {
+ serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue());
+ serializer, mCurrentResolvedPolicy.getValue());
+ serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
}
for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue());
+ serializer, mPoliciesSetByAdmins.get(admin).getValue());
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
admin.saveToXml(serializer);
@@ -217,32 +226,57 @@
static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
+ PolicyDefinition<V> policyDefinition = null;
- PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
- parser, ATTR_RESOLVED_POLICY);
+ PolicyValue<V> currentResolvedPolicy = null;
LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
- if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
- PolicyValue<V> value = policyDefinition.readPolicyValueFromXml(
- parser, ATTR_POLICY_VALUE);
- EnforcingAdmin admin;
- int adminPolicyDepth = parser.getDepth();
- if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
- && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
- admin = EnforcingAdmin.readFromXml(parser);
- policiesSetByAdmins.put(admin, value);
- }
- } else {
- Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
+ switch (tag) {
+ case TAG_ADMIN_POLICY_ENTRY:
+ PolicyValue<V> value = null;
+ EnforcingAdmin admin = null;
+ int adminPolicyDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) {
+ String adminPolicyTag = parser.getName();
+ switch (adminPolicyTag) {
+ case TAG_ENFORCING_ADMIN_ENTRY:
+ admin = EnforcingAdmin.readFromXml(parser);
+ break;
+ case TAG_POLICY_VALUE_ENTRY:
+ value = policyDefinition.readPolicyValueFromXml(parser);
+ break;
+ }
+ }
+ if (admin != null && value != null) {
+ policiesSetByAdmins.put(admin, value);
+ } else {
+ Log.e(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY");
+ }
+ break;
+ case TAG_POLICY_DEFINITION_ENTRY:
+ policyDefinition = PolicyDefinition.readFromXml(parser);
+ break;
+
+ case TAG_RESOLVED_VALUE_ENTRY:
+ currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag: " + tag);
}
}
- return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+ if (policyDefinition != null) {
+ return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+ } else {
+ Log.e("PolicyState", "Error parsing policyState");
+ return null;
+ }
}
+
+
PolicyDefinition<V> getPolicyDefinition() {
return mPolicyDefinition;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
index dc6592d..24d0521 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
@@ -36,21 +36,17 @@
private static final String ATTR_VALUES_SEPARATOR = ";";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Set<String> value) throws IOException {
Objects.requireNonNull(value);
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_VALUES,
- String.join(ATTR_VALUES_SEPARATOR, value));
+ /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value));
}
@Nullable
@Override
- PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
- String valuesStr = parser.getAttributeValue(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_VALUES);
+ PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
+ String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
if (valuesStr == null) {
Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
return null;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 54d2c19..db84b6c 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -1339,7 +1339,7 @@
/** Adds {@code listener} to be notified on conversation changes. */
public void addConversationsListener(
@NonNull PeopleService.ConversationsListener listener) {
- synchronized (mConversationsListeners) {
+ synchronized (mLock) {
mConversationsListeners.add(Objects.requireNonNull(listener));
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml
index ddc8dfd..1ed3c7b 100644
--- a/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml
@@ -75,7 +75,8 @@
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index ea0481e..aa3930a 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -27,7 +27,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0c512d2..318067e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -91,8 +91,8 @@
import org.junit.Test;
import org.mockito.Mock;
-import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
+import java.io.Writer;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
@@ -596,7 +596,7 @@
// about the actual output, just that we don't crash
queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED, "Test-driven");
queue.dumpLocked(SystemClock.uptimeMillis(),
- new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+ new IndentingPrintWriter(new PrintWriter(Writer.nullWriter())));
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
@@ -1166,6 +1166,11 @@
List<Intent> intents) {
for (int i = 0; i < intents.size(); i++) {
queue.makeActiveNextPending();
+
+ // While we're here, give our health check some test coverage
+ queue.assertHealthLocked();
+ queue.dumpLocked(0L, new IndentingPrintWriter(Writer.nullWriter()));
+
final Intent actualIntent = queue.getActive().intent;
final Intent expectedIntent = intents.get(i);
final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent
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 cbc2597..b6bc02a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -39,6 +39,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -106,10 +107,10 @@
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -231,6 +232,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
+ assertHealth();
final ProcessStartBehavior behavior = mNextProcessStartBehavior
.getAndSet(ProcessStartBehavior.SUCCESS);
if (behavior == ProcessStartBehavior.FAIL_NULL) {
@@ -462,6 +464,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ assertHealth();
final Intent intent = invocation.getArgument(0);
final Bundle extras = invocation.getArgument(5);
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
@@ -483,6 +486,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ assertHealth();
final Intent intent = invocation.getArgument(1);
final Bundle extras = invocation.getArgument(4);
final boolean ordered = invocation.getArgument(5);
@@ -600,6 +604,13 @@
BackgroundStartPrivileges.NONE, false, null);
}
+ private void assertHealth() {
+ if (mImpl == Impl.MODERN) {
+ // If this fails, it'll throw a clear reason message
+ ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
+ }
+ }
+
private static Map<String, Object> asMap(Bundle bundle) {
final Map<String, Object> map = new HashMap<>();
if (bundle != null) {
@@ -769,7 +780,7 @@
// about the actual output, just that we don't crash
mQueue.dumpDebug(new ProtoOutputStream(),
ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+ mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(Writer.nullWriter()),
null, 0, true, true, true, null, false);
mQueue.dumpToDropBoxLocked(TAG);
@@ -1166,7 +1177,7 @@
// about the actual output, just that we don't crash
mQueue.dumpDebug(new ProtoOutputStream(),
ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+ mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(Writer.nullWriter()),
null, 0, true, true, true, null, false);
}
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 fc503b7..45fefe4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -804,7 +804,7 @@
when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+ when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn(
DisplayDeviceConfig.DEFAULT_ID);
when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
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 c021ef6..d9133a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -809,7 +809,7 @@
when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+ when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn(
DisplayDeviceConfig.DEFAULT_ID);
when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index 03f667f..df2f59a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -23,6 +23,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +41,7 @@
import android.os.UserHandle;
import com.android.server.LocalServices;
+import com.android.server.job.controllers.JobStatus;
import com.android.server.notification.NotificationManagerInternal;
import org.junit.After;
@@ -493,6 +496,44 @@
eq(notificationId), eq(UserHandle.getUserId(uid)));
}
+ @Test
+ public void testUserInitiatedJob_hasNotificationFlag() {
+ final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+ final JobServiceContext jsc = mock(JobServiceContext.class);
+ final JobStatus js = mock(JobStatus.class);
+ js.startedAsUserInitiatedJob = true;
+ doReturn(js).when(jsc).getRunningJobLocked();
+ final Notification notification = createValidNotification();
+ final int uid = 10123;
+ final int pid = 42;
+ final int notificationId = 23;
+
+ coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+ JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+ verify(mNotificationManagerInternal)
+ .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+ assertNotEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0);
+ }
+
+ @Test
+ public void testNonUserInitiatedJob_doesNotHaveNotificationFlag() {
+ final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+ final JobServiceContext jsc = mock(JobServiceContext.class);
+ doReturn(mock(JobStatus.class)).when(jsc).getRunningJobLocked();
+ final Notification notification = createValidNotification();
+ final int uid = 10123;
+ final int pid = 42;
+ final int notificationId = 23;
+
+ coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+ JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+ verify(mNotificationManagerInternal)
+ .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+ assertEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0);
+ }
+
private Notification createValidNotification() {
final Notification notification = mock(Notification.class);
doReturn(mock(Icon.class)).when(notification).getSmallIcon();
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 6f26a5f..cfeaf0b 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -42,6 +42,7 @@
"androidx.test.ext.truth",
"androidx.test.runner",
"androidx.test.rules",
+ "androidx.test.ext.junit",
"cts-wm-util",
"platform-compat-test-rules",
"mockito-target-minus-junit4",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6861c2f..107dde2 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -74,7 +74,8 @@
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index e4aed97..07c6182 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -30,6 +30,7 @@
import android.app.StatusBarManager;
import android.content.Intent;
+import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricContextListener.FoldState;
import android.hardware.biometrics.common.OperationContext;
@@ -59,6 +60,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
@Presubmit
@@ -102,22 +104,52 @@
@Test
public void testIsAod() throws RemoteException {
- mListener.onDozeChanged(true /* isAod */, false /* isAwake */);
- assertThat(mProvider.isAod()).isTrue();
- mListener.onDozeChanged(false /* isAod */, false /* isAwake */);
- assertThat(mProvider.isAod()).isFalse();
+ final Map<Integer, Boolean> expectedAod = Map.of(
+ AuthenticateOptions.DISPLAY_STATE_UNKNOWN, false,
+ AuthenticateOptions.DISPLAY_STATE_AOD, true,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN, false,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI, false,
+ AuthenticateOptions.DISPLAY_STATE_SCREENSAVER, false
+ );
+
+ for (Map.Entry<Integer, Boolean> entry : expectedAod.entrySet()) {
+ mListener.onDisplayStateChanged(entry.getKey());
+
+ assertThat(mProvider.isAod()).isEqualTo(entry.getValue());
+ }
}
@Test
public void testIsAwake() throws RemoteException {
- mListener.onDozeChanged(false /* isAod */, true /* isAwake */);
- assertThat(mProvider.isAwake()).isTrue();
- mListener.onDozeChanged(false /* isAod */, false /* isAwake */);
- assertThat(mProvider.isAwake()).isFalse();
- mListener.onDozeChanged(true /* isAod */, true /* isAwake */);
- assertThat(mProvider.isAwake()).isTrue();
- mListener.onDozeChanged(true /* isAod */, false /* isAwake */);
- assertThat(mProvider.isAwake()).isFalse();
+ final Map<Integer, Boolean> expectedAwake = Map.of(
+ AuthenticateOptions.DISPLAY_STATE_UNKNOWN, true,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN, true,
+ AuthenticateOptions.DISPLAY_STATE_SCREENSAVER, true,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI, false,
+ AuthenticateOptions.DISPLAY_STATE_AOD, false
+ );
+
+ for (Map.Entry<Integer, Boolean> entry : expectedAwake.entrySet()) {
+ mListener.onDisplayStateChanged(entry.getKey());
+
+ assertThat(mProvider.isAwake()).isEqualTo(entry.getValue());
+ }
+ }
+
+ @Test
+ public void testGetDisplayState() throws RemoteException {
+ final List<Integer> states = List.of(
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_SCREENSAVER,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_UNKNOWN);
+
+ for (int state : states) {
+ mListener.onDisplayStateChanged(state);
+
+ assertThat(mProvider.getDisplayState()).isEqualTo(state);
+ }
}
@Test
@@ -147,21 +179,54 @@
}
@Test
+ public void testSubscribesToDisplayState() throws RemoteException {
+ final List<Integer> actual = new ArrayList<>();
+ final List<Integer> expected = List.of(AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
+
+ mProvider.subscribe(mOpContext, ctx -> {
+ assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
+ assertThat(mProvider.getDisplayState()).isEqualTo(ctx.displayState);
+ actual.add(ctx.displayState);
+ });
+
+ for (int v : expected) {
+ mListener.onDisplayStateChanged(v);
+ }
+
+ assertThat(actual).containsExactly(
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN).inOrder();
+ }
+
+ @Test
public void testSubscribesToAod() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
mProvider.subscribe(mOpContext, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
- assertThat(mProvider.isAwake()).isFalse();
actual.add(ctx.isAod);
});
- for (boolean v : List.of(true, false, true, true, false, false)) {
- mListener.onDozeChanged(v /* isDozing */, false /* isAwake */);
+ for (int v : List.of(
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)) {
+ mListener.onDisplayStateChanged(v);
}
- assertThat(actual).containsExactly(true, false, true, false).inOrder();
+ assertThat(actual).containsExactly(true, false, true, false, false).inOrder();
}
@Test
@@ -170,16 +235,20 @@
mProvider.subscribe(mOpContext, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
- assertThat(ctx.isAod).isFalse();
- assertThat(mProvider.isAod()).isFalse();
actual.add(mProvider.isAwake());
});
- for (boolean v : List.of(true, false, true, true, false, false)) {
- mListener.onDozeChanged(false /* isDozing */, v /* isAwake */);
+ for (int v : List.of(
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_SCREENSAVER,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_AOD,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI)) {
+ mListener.onDisplayStateChanged(v);
}
- assertThat(actual).containsExactly(true, false, true, false).inOrder();
+ assertThat(actual).containsExactly(true, false, true, true, false, false).inOrder();
}
@Test
@@ -188,13 +257,13 @@
mProvider.subscribe(mOpContext, emptyConsumer);
mProvider.unsubscribe(mOpContext);
- mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
+ mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
mProvider.subscribe(mOpContext, nonEmptyConsumer);
- mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
+ mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
mProvider.unsubscribe(mOpContext);
- mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
+ mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_NO_UI);
verify(emptyConsumer, never()).accept(any());
verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext()));
@@ -235,7 +304,7 @@
@Test
public void testUpdate() throws RemoteException {
- mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
+ mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_NO_UI);
OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */);
OperationContext aidlContext = context.toAidlContext();
@@ -252,7 +321,8 @@
final int id = 40 + type;
final boolean aod = (type & 1) == 0;
- mListener.onDozeChanged(aod /* isDozing */, false /* isAwake */);
+ mListener.onDisplayStateChanged(aod ? AuthenticateOptions.DISPLAY_STATE_AOD
+ : AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
context = mProvider.updateContext(mOpContext, false /* crypto */);
aidlContext = context.toAidlContext();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index c7962c8..c652b74 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -19,7 +19,9 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Intent;
+import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.DisplayState;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
import android.platform.test.annotations.Presubmit;
@@ -37,6 +39,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Map;
+
@Presubmit
@SmallTest
public class OperationContextExtTest {
@@ -59,15 +63,18 @@
final int id = 5;
final byte reason = OperationReason.UNKNOWN;
+ final int displayState = DisplayState.NO_UI;
aidlContext.id = id;
aidlContext.isAod = true;
aidlContext.isCrypto = true;
aidlContext.reason = reason;
+ aidlContext.displayState = displayState;
assertThat(context.getId()).isEqualTo(id);
assertThat(context.isAod()).isTrue();
assertThat(context.isCrypto()).isTrue();
assertThat(context.getReason()).isEqualTo(reason);
+ assertThat(context.getDisplayState()).isEqualTo(displayState);
}
@Test
@@ -78,6 +85,25 @@
}
@Test
+ public void mapsDisplayStatesToAidl() {
+ final Map<Integer, Integer> map = Map.of(
+ AuthenticateOptions.DISPLAY_STATE_UNKNOWN, DisplayState.UNKNOWN,
+ AuthenticateOptions.DISPLAY_STATE_AOD, DisplayState.AOD,
+ AuthenticateOptions.DISPLAY_STATE_NO_UI, DisplayState.NO_UI,
+ AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN, DisplayState.LOCKSCREEN,
+ AuthenticateOptions.DISPLAY_STATE_SCREENSAVER, DisplayState.SCREENSAVER,
+ 100, DisplayState.UNKNOWN
+ );
+
+ for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
+ final OperationContextExt context = new OperationContextExt(newAidlContext());
+ when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
+ assertThat(context.update(mBiometricContext).getDisplayState())
+ .isEqualTo(entry.getValue());
+ }
+ }
+
+ @Test
public void updatesFromSourceForKeyguard() {
final BiometricContextSessionInfo info =
new BiometricContextSessionInfo(InstanceId.fakeInstanceId(9));
@@ -102,11 +128,13 @@
final int rotation = Surface.ROTATION_270;
final int foldState = IBiometricContextListener.FoldState.HALF_OPENED;
final int dockState = Intent.EXTRA_DOCK_STATE_CAR;
+ final int displayState = AuthenticateOptions.DISPLAY_STATE_AOD;
when(mBiometricContext.getCurrentRotation()).thenReturn(rotation);
when(mBiometricContext.getFoldState()).thenReturn(foldState);
when(mBiometricContext.getDockedState()).thenReturn(dockState);
when(mBiometricContext.isDisplayOn()).thenReturn(true);
+ when(mBiometricContext.getDisplayState()).thenReturn(displayState);
final OperationContextExt context = new OperationContextExt(newAidlContext());
@@ -124,6 +152,7 @@
assertThat(context.getFoldState()).isEqualTo(foldState);
assertThat(context.getOrientation()).isEqualTo(rotation);
assertThat(context.isDisplayOn()).isTrue();
+ assertThat(context.getDisplayState()).isEqualTo(DisplayState.AOD);
}
private static OperationContext newAidlContext() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 6b0e330..7468901 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -68,6 +68,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
@Presubmit
@SmallTest
@@ -106,6 +107,8 @@
private AuthSessionCoordinator mAuthSessionCoordinator;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -145,6 +148,28 @@
}
@Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ final ArgumentCaptor<OperationContext> captor =
+ ArgumentCaptor.forClass(OperationContext.class);
+ verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
+ OperationContext opContext = captor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(
+ mOperationContextCaptor.capture(), mContextInjector.capture());
+ assertThat(opContext).isSameInstanceAs(
+ mOperationContextCaptor.getValue().toAidlContext());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(same(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+ }
+
+ @Test
public void cancelsAuthWhenNotInForeground() throws Exception {
final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index 0abfa7e..c26eee9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -56,6 +56,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.function.Consumer;
+
@Presubmit
@SmallTest
public class FaceDetectClientTest {
@@ -84,6 +86,8 @@
private Sensor.HalSessionCallback mHalSessionCallback;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -121,6 +125,32 @@
verify(mHal, never()).detectInteraction();
}
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FaceDetectClient client = createClient();
+ client.start(mCallback);
+
+ final ArgumentCaptor<OperationContext> captor =
+ ArgumentCaptor.forClass(OperationContext.class);
+ verify(mHal).detectInteractionWithContext(captor.capture());
+ OperationContext opContext = captor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(
+ mOperationContextCaptor.capture(), mContextInjector.capture());
+ assertThat(opContext).isSameInstanceAs(
+ mOperationContextCaptor.getValue().toAidlContext());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(same(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+ }
+
+ private FaceDetectClient createClient() throws RemoteException {
+ return createClient(100 /* version */);
+ }
+
private FaceDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index d75aca1..54d116f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyByte;
@@ -25,6 +27,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
@@ -52,6 +55,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.function.Consumer;
+
@Presubmit
@SmallTest
public class FaceEnrollClientTest {
@@ -81,6 +86,8 @@
private Sensor.HalSessionCallback mHalSessionCallback;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -108,11 +115,38 @@
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(),
- same(mOperationContextCaptor.getValue().toAidlContext()));
+
+ final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+ order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), same(aidlContext));
verify(mHal, never()).enroll(any(), anyByte(), any(), any());
}
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FaceEnrollClient client = createClient();
+ client.start(mCallback);
+
+ final ArgumentCaptor<OperationContext> captor =
+ ArgumentCaptor.forClass(OperationContext.class);
+ verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), captor.capture());
+ OperationContext opContext = captor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(
+ mOperationContextCaptor.capture(), mContextInjector.capture());
+ assertThat(opContext).isSameInstanceAs(
+ mOperationContextCaptor.getValue().toAidlContext());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(same(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+ }
+
+ private FaceEnrollClient createClient() throws RemoteException {
+ return createClient(200 /* version */);
+ }
+
private FaceEnrollClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c664500..f8f40fe 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -338,7 +338,7 @@
assertThat(opContext).isSameInstanceAs(
mOperationContextCaptor.getValue().toAidlContext());
mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(eq(opContext));
+ verify(mHal).onContextChanged(same(opContext));
client.stopHalOperation();
verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index c20cc39..6dfdd87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -55,6 +55,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.function.Consumer;
+
@Presubmit
@SmallTest
public class FingerprintDetectClientTest {
@@ -83,6 +85,8 @@
private Sensor.HalSessionCallback mHalSessionCallback;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -121,6 +125,32 @@
verify(mHal, never()).detectInteraction();
}
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FingerprintDetectClient client = createClient();
+ client.start(mCallback);
+
+ final ArgumentCaptor<OperationContext> captor =
+ ArgumentCaptor.forClass(OperationContext.class);
+ verify(mHal).detectInteractionWithContext(captor.capture());
+ OperationContext opContext = captor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(
+ mOperationContextCaptor.capture(), mContextInjector.capture());
+ assertThat(opContext).isSameInstanceAs(
+ mOperationContextCaptor.getValue().toAidlContext());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(same(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+ }
+
+ private FingerprintDetectClient createClient() throws RemoteException {
+ return createClient(200 /* version */);
+ }
+
private FingerprintDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 7646c40..3c89278 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -110,8 +108,6 @@
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
- private ArgumentCaptor<PointerContext> mPointerContextCaptor;
- @Captor
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Rule
@@ -246,7 +242,7 @@
mOperationContextCaptor.capture(), mContextInjector.capture());
mContextInjector.getValue().accept(
mOperationContextCaptor.getValue().toAidlContext());
- verify(mHal).onContextChanged(eq(opContext));
+ verify(mHal).onContextChanged(same(opContext));
client.stopHalOperation();
verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index fc25152..3bef413 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -126,7 +126,7 @@
return mClock::now;
}
- }, // pass in test looper instead, pass in offsetable clock
+ }, // pass in test looper instead, pass in offsettable clock
() -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor,
mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
@@ -300,6 +300,179 @@
}
@Test
+ public void testShortTermModelTimesOut() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Sensor reads 123 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+ // User sets brightness to 100
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+
+ when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+
+ mController.switchToIdleMode();
+ when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+ when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+ 123f, 0.5f)).thenReturn(true);
+
+ // Sensor reads 1000 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+ mTestLooper.moveTimeForward(
+ mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
+ mTestLooper.dispatchAll();
+
+ mController.switchToInteractiveScreenBrightnessMode();
+ mTestLooper.moveTimeForward(4000);
+ mTestLooper.dispatchAll();
+
+ // Verify only happens on the first configure. (i.e. not again when switching back)
+ // Intentionally using any() to ensure it's not called whatsoever.
+ verify(mBrightnessMappingStrategy, times(1))
+ .addUserDataPoint(123.0f, 0.5f);
+ verify(mBrightnessMappingStrategy, times(1))
+ .addUserDataPoint(anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testShortTermModelDoesntTimeOut() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Sensor reads 123 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+ // User sets brightness to 100
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+
+ when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+ anyFloat(), anyFloat())).thenReturn(true);
+ when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+ when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
+ when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
+
+ mController.switchToIdleMode();
+ when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+
+ // Time does not move forward, since clock is doesn't increment naturally.
+ mTestLooper.dispatchAll();
+
+ // Sensor reads 100000 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
+ mController.switchToInteractiveScreenBrightnessMode();
+
+ // Verify short term model is not reset.
+ verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
+
+ // Verify that we add the data point once when the user sets it, and again when we return
+ // interactive mode.
+ verify(mBrightnessMappingStrategy, times(2))
+ .addUserDataPoint(123.0f, 0.51f);
+ }
+
+ @Test
+ public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Sensor reads 123 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+ // User sets brightness to 100
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+
+ when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+ when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
+ when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
+
+ mController.switchToIdleMode();
+ when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+ when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
+ when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+ when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+ 123f, 0.5f)).thenReturn(true);
+
+ // Sensor reads 1000 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+ mTestLooper.moveTimeForward(
+ mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
+ mTestLooper.dispatchAll();
+
+ mController.switchToInteractiveScreenBrightnessMode();
+ mTestLooper.moveTimeForward(4000);
+ mTestLooper.dispatchAll();
+
+ // Verify only happens on the first configure. (i.e. not again when switching back)
+ // Intentionally using any() to ensure it's not called whatsoever.
+ verify(mBrightnessMappingStrategy, times(1))
+ .addUserDataPoint(123.0f, 0.5f);
+ verify(mBrightnessMappingStrategy, times(1))
+ .addUserDataPoint(anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Sensor reads 123 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+ // User sets brightness to 100
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+
+ when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+
+ when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
+ when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
+
+ mController.switchToIdleMode();
+ when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+ when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
+ when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+
+ when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+ 123f, 0.5f)).thenReturn(true);
+
+ // Sensor reads 1000 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+ // Do not fast-forward time.
+ mTestLooper.dispatchAll();
+
+ mController.switchToInteractiveScreenBrightnessMode();
+ // Do not fast-forward time
+ mTestLooper.dispatchAll();
+
+ // Verify this happens on the first configure and again when switching back
+ // Intentionally using any() to ensure it's not called any other times whatsoever.
+ verify(mBrightnessMappingStrategy, times(2))
+ .addUserDataPoint(123.0f, 0.5f);
+ verify(mBrightnessMappingStrategy, times(2))
+ .addUserDataPoint(anyFloat(), anyFloat());
+ }
+
+ @Test
public void testSwitchToIdleMappingStrategy() throws Exception {
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -326,6 +499,11 @@
// Called once for init, and once when switching,
// setAmbientLux() is called twice and once in updateAutoBrightness()
verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
+ // Called when switching.
+ verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
+ verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
+ verify(mBrightnessMappingStrategy, times(1)).getUserLux();
+
// Ensure, after switching, original BMS is not used anymore
verifyNoMoreInteractions(mBrightnessMappingStrategy);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index ffe2fec..46956d7 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -41,8 +41,8 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.mode.DisplayModeDirectorTest;
import org.junit.Before;
@@ -54,6 +54,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
@SmallTest
@@ -92,7 +93,7 @@
/////////////////
@Test
- public void testBrightnessThrottlingData() {
+ public void testThermalBrightnessThrottlingData() {
List<ThrottlingLevel> singleLevel = new ArrayList<>();
singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
@@ -119,34 +120,32 @@
PowerManager.BRIGHTNESS_MAX + EPSILON));
// Test invalid data
- BrightnessThrottlingData data;
- data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null);
+ ThermalBrightnessThrottlingData data;
+ data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
assertEquals(data, null);
- data = BrightnessThrottlingData.create((BrightnessThrottlingData)null);
+ data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
assertEquals(data, null);
- data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+ data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
assertEquals(data, null);
- data = BrightnessThrottlingData.create(unsortedThermalLevels);
+ data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
assertEquals(data, null);
- data = BrightnessThrottlingData.create(unsortedBrightnessLevels);
+ data = ThermalBrightnessThrottlingData.create(unsortedLevels);
assertEquals(data, null);
- data = BrightnessThrottlingData.create(unsortedLevels);
- assertEquals(data, null);
- data = BrightnessThrottlingData.create(invalidLevel);
+ data = ThermalBrightnessThrottlingData.create(invalidLevel);
assertEquals(data, null);
// Test valid data
- data = BrightnessThrottlingData.create(singleLevel);
+ data = ThermalBrightnessThrottlingData.create(singleLevel);
assertNotEquals(data, null);
assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
- data = BrightnessThrottlingData.create(validLevels);
+ data = ThermalBrightnessThrottlingData.create(validLevels);
assertNotEquals(data, null);
assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
}
@Test
- public void testThrottlingUnsupported() throws Exception {
+ public void testThermalThrottlingUnsupported() {
final BrightnessThrottler throttler = createThrottlerUnsupported();
assertFalse(throttler.deviceSupportsThrottling());
@@ -158,13 +157,13 @@
}
@Test
- public void testThrottlingSingleLevel() throws Exception {
+ public void testThermalThrottlingSingleLevel() throws Exception {
final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
0.25f);
List<ThrottlingLevel> levels = new ArrayList<>();
levels.add(level);
- final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
final BrightnessThrottler throttler = createThrottlerSupported(data);
assertTrue(throttler.deviceSupportsThrottling());
@@ -213,7 +212,7 @@
}
@Test
- public void testThrottlingMultiLevel() throws Exception {
+ public void testThermalThrottlingMultiLevel() throws Exception {
final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
0.62f);
final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
@@ -222,7 +221,7 @@
List<ThrottlingLevel> levels = new ArrayList<>();
levels.add(levelLo);
levels.add(levelHi);
- final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
final BrightnessThrottler throttler = createThrottlerSupported(data);
assertTrue(throttler.deviceSupportsThrottling());
@@ -293,51 +292,32 @@
assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
}
- @Test public void testUpdateThrottlingData() throws Exception {
+ @Test public void testUpdateThermalThrottlingData() throws Exception {
// Initialise brightness throttling levels
// Ensure that they are overridden by setting the data through device config.
final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
0.25f);
List<ThrottlingLevel> levels = new ArrayList<>();
levels.add(level);
- final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
- mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4");
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
final BrightnessThrottler throttler = createThrottlerSupported(data);
verify(mThermalServiceMock).registerThermalEventListenerWithType(
mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
- // Set status too low to trigger throttling
- listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
- mTestLooper.dispatchAll();
- assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
- assertFalse(throttler.isThrottled());
-
- // Set status high enough to trigger throttling
- listener.notifyThrottling(getSkinTemp(level.thermalStatus));
- mTestLooper.dispatchAll();
- assertEquals(0.4f, throttler.getBrightnessCap(), 0f);
- assertTrue(throttler.isThrottled());
-
- // Update thresholds
- // This data is equivalent to the string "123,1,critical,0.8", passed below
- final ThrottlingLevel newLevel = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
- 0.8f);
// Set new (valid) data from device config
- mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8");
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
- // Set status too low to trigger throttling
- listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus - 1));
- mTestLooper.dispatchAll();
- assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
- assertFalse(throttler.isThrottled());
-
- // Set status high enough to trigger throttling
- listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus));
- mTestLooper.dispatchAll();
- assertEquals(newLevel.brightness, throttler.getBrightnessCap(), 0f);
- assertTrue(throttler.isThrottled());
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
+ "123,1,critical,0.75;123,1,critical,0.99,id_2");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
+ "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
}
@Test public void testInvalidThrottlingStrings() throws Exception {
@@ -347,33 +327,54 @@
0.25f);
List<ThrottlingLevel> levels = new ArrayList<>();
levels.add(level);
- final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
final BrightnessThrottler throttler = createThrottlerSupported(data);
verify(mThermalServiceMock).registerThermalEventListenerWithType(
mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
// None of these are valid so shouldn't override the original data
- mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4"); // Not the current id
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4"); // Incorrect number
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4"); // Incorrect number
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4"); // Invalid level
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3"); // Invalid brightness
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData("invalid string"); // Invalid format
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
- mDeviceConfigFake.setBrightnessThrottlingData(""); // Invalid format
- testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+
+ // Not the current id
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Incorrect number
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Incorrect number
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid level
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid brightness
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid brightness
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid format
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid format
+ mDeviceConfigFake.setThermalBrightnessThrottlingData("");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid string format
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
+ "123,default,1,critical,0.75,1,critical,0.99");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid level string and number string
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
+ "123,1,1,critical,0.75,id_2,1,critical,0.99");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ // Invalid format - (two default ids for same display)
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
+ "123,1,critical,0.75,default;123,1,critical,0.99");
+ testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
}
- private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener,
- float tooLowCap, float tooHighCap) throws Exception {
+ private void testThermalThrottling(BrightnessThrottler throttler,
+ IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
tooHighCap);
@@ -396,7 +397,7 @@
0.25f);
List<ThrottlingLevel> levels = new ArrayList<>();
levels.add(level);
- final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
// These are identical to the string set below
final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
@@ -406,7 +407,7 @@
final ThrottlingLevel levelEmergency = new ThrottlingLevel(
PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
- mDeviceConfigFake.setBrightnessThrottlingData(
+ mDeviceConfigFake.setThermalBrightnessThrottlingData(
"123,3,severe,0.9,critical,0.5,emergency,0.1");
final BrightnessThrottler throttler = createThrottlerSupported(data);
@@ -472,13 +473,18 @@
}
private BrightnessThrottler createThrottlerUnsupported() {
- return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, null, () -> {}, null);
+ return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
+ /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
+ /* thermalThrottlingDataId= */ null,
+ /* thermalThrottlingDataMap= */ new HashMap<>(1));
}
- private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
+ private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
assertNotNull(data);
+ HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+ throttlingDataMap.put("default", data);
return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
- data, () -> {}, "123");
+ () -> {}, "123", "default", throttlingDataMap);
}
private Temperature getSkinTemp(@ThrottlingStatus int status) {
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 a7d3df9..130e6ad 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -84,11 +84,11 @@
}
@Test
- public void testBrightnessThrottlingMapId() {
+ public void testThermalBrightnessThrottlingMapId() {
Layout configLayout = mDeviceStateToLayoutMap.get(2);
- assertEquals("concurrent1", configLayout.getAt(0).getBrightnessThrottlingMapId());
- assertEquals("concurrent2", configLayout.getAt(1).getBrightnessThrottlingMapId());
+ assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
+ assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
}
@Test
@@ -108,7 +108,7 @@
}
@Test
- public void testRefreshRateThermalThrottlingMapId() {
+ public void testThermalRefreshRateThrottlingMapId() {
Layout configLayout = mDeviceStateToLayoutMap.get(4);
assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
@@ -124,12 +124,14 @@
/* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
/* brightnessThrottlingMapId= */ "brightness1",
- /* refreshRateZoneId= */ "zone1", /* refreshRateThermalThrottlingMapId= */ "rr1");
+ /* refreshRateZoneId= */ "zone1",
+ /* refreshRateThermalThrottlingMapId= */ "rr1");
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
/* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
/* brightnessThrottlingMapId= */ "brightness2",
- /* refreshRateZoneId= */ "zone2", /* refreshRateThermalThrottlingMapId= */ "rr2");
+ /* refreshRateZoneId= */ "zone2",
+ /* refreshRateThermalThrottlingMapId= */ "rr2");
assertEquals(testLayout, configLayout);
}
@@ -147,7 +149,8 @@
layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null);
}
private void setupDeviceStateToLayoutMap() throws IOException {
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 9fd647b..5837b21 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -50,6 +50,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
@SmallTest
@@ -186,50 +187,72 @@
assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
- List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList();
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+ defaultThrottlingLevels = new ArrayList<>();
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ defaultThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
));
- assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getBrightnessThrottlingData("default"));
- throttlingLevels.clear();
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
+
+ List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+ concurrentThrottlingLevels = new ArrayList<>();
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
));
- throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+ concurrentThrottlingLevels.add(
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
));
- assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getBrightnessThrottlingData("concurrent"));
+ DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
+ new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
+
+ HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
+ new HashMap<>(2);
+ throttlingDataMap.put("default", defaultThrottlingData);
+ throttlingDataMap.put("concurrent", concurrentThrottlingData);
+
+ assertEquals(throttlingDataMap,
+ mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
@@ -246,8 +269,7 @@
mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
SMALL_DELTA);
-
- // Todo: Add asserts for BrightnessThrottlingData, DensityMapping,
+ // Todo: Add asserts for DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -329,16 +351,16 @@
assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
- // Todo: Add asserts for BrightnessThrottlingData, DensityMapping,
+ // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@Test
- public void testRefreshRateThermalThrottlingFromDisplayConfig() throws IOException {
+ public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
- mDisplayDeviceConfig.getRefreshRateThrottlingData(null);
+ mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
assertNotNull(defaultMap);
assertEquals(2, defaultMap.size());
assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
@@ -347,7 +369,7 @@
assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
SparseArray<SurfaceControl.RefreshRateRange> testMap =
- mDisplayDeviceConfig.getRefreshRateThrottlingData("test");
+ mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
assertNotNull(testMap);
assertEquals(1, testMap.size());
assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index b5237a5..acfc073 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -37,6 +37,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
@@ -289,7 +290,7 @@
}
@Test
- public void testCreateVirtualDisplay_sentToInputManager() {
+ public void testCreateVirtualDisplay_sentToInputManager() throws RemoteException {
// This is to update the display device config such that DisplayManagerService can ignore
// the usage of SensorManager, which is available only after the PowerManagerService
// is ready.
@@ -316,7 +317,8 @@
builder.setFlags(flags);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -442,7 +444,8 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -455,7 +458,7 @@
}
@Test
- public void testCreateVirtualDisplayOwnFocus() {
+ public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
@@ -479,7 +482,8 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -492,7 +496,7 @@
}
@Test
- public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+ public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() throws RemoteException {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
@@ -513,7 +517,8 @@
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -755,7 +760,8 @@
builder.setUniqueId(uniqueId);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -767,7 +773,8 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -805,7 +812,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. This should be added to the previously created display
@@ -821,7 +829,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertEquals(
@@ -859,7 +868,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
@@ -878,7 +888,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertNotEquals(
@@ -922,7 +933,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -948,7 +960,8 @@
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -972,7 +985,8 @@
null /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
// Check that FLAG_ALWAYS_UNLOCKED is not set.
assertEquals(
@@ -1004,7 +1018,8 @@
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -1016,7 +1031,8 @@
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -1032,14 +1048,50 @@
}
@Test
+ public void testCreateVirtualDisplay_isValidProjection_notValid()
+ throws RemoteException {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(false).when(projection).isValid();
+ when(mMockProjectionService
+ .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+ .thenReturn(true);
+ doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- isValid false");
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ // Pass in a non-null projection.
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+ // VirtualDisplay is created for mirroring.
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+ any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+ // But mirroring doesn't begin.
+ verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+ mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+ ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+ assertThat(session.isWaitingToRecord()).isTrue();
+ }
+
+ @Test
public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
throws RemoteException {
final int displayToRecord = 50;
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mMockWindowManagerInternal
- .setContentRecordingSession(any(ContentRecordingSession.class)))
- .thenReturn(true);
IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(projection).isValid();
+ when(mMockProjectionService
+ .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+ .thenReturn(true);
doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
@@ -1056,21 +1108,23 @@
mMockAppToken /* callback */, projection, PACKAGE_NAME);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
- verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
- mContentRecordingSessionCaptor.capture());
+ verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+ mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
+ assertThat(session.isWaitingToRecord()).isFalse();
}
@Test
public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mMockWindowManagerInternal
- .setContentRecordingSession(any(ContentRecordingSession.class)))
- .thenReturn(false);
IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(projection).isValid();
+ when(mMockProjectionService
+ .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+ .thenReturn(false);
doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
@@ -1093,12 +1147,12 @@
throws RemoteException {
final int displayToRecord = 50;
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mMockWindowManagerInternal
- .setContentRecordingSession(any(ContentRecordingSession.class)))
- .thenReturn(true);
IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(projection).isValid();
+ when(mMockProjectionService
+ .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+ .thenReturn(true);
doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
-
doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
@@ -1115,8 +1169,8 @@
mMockAppToken /* callback */, projection, PACKAGE_NAME);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
- verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
- mContentRecordingSessionCaptor.capture());
+ verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+ mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
@@ -1124,7 +1178,8 @@
}
@Test
- public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags() {
+ public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags()
+ throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
// Set no flags for the VirtualDisplay.
@@ -1143,12 +1198,13 @@
// VirtualDisplay is created but not for mirroring.
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
- any(ContentRecordingSession.class));
+ verify(mMockProjectionService, never()).setContentRecordingSession(
+ any(ContentRecordingSession.class), nullable(IMediaProjection.class));
}
@Test
- public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag() {
+ public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag()
+ throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
// Set a non-mirroring flag for the VirtualDisplay.
@@ -1168,18 +1224,19 @@
// VirtualDisplay is created but not for mirroring.
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
- any(ContentRecordingSession.class));
+ verify(mMockProjectionService, never()).setContentRecordingSession(
+ any(ContentRecordingSession.class), nullable(IMediaProjection.class));
}
@Test
public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mMockWindowManagerInternal
- .setContentRecordingSession(any(ContentRecordingSession.class)))
- .thenReturn(true);
IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(projection).isValid();
+ when(mMockProjectionService
+ .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+ .thenReturn(true);
doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
// Set no flags for the VirtualDisplay.
@@ -1198,8 +1255,8 @@
// VirtualDisplay is created for mirroring.
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
- verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
- any(ContentRecordingSession.class));
+ verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+ any(ContentRecordingSession.class), nullable(IMediaProjection.class));
}
/**
@@ -1228,7 +1285,8 @@
builder.setUniqueId(uniqueId);
final int displayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -1243,7 +1301,8 @@
* ADD_TRUSTED_DISPLAY is granted.
*/
@Test
- public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission() {
+ public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
+ throws RemoteException {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
@@ -1261,7 +1320,8 @@
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
@@ -1329,7 +1389,8 @@
int displayId = localService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
- verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
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 567548e..7536c79 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -649,9 +649,9 @@
assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
.getLeadDisplayIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(1, false);
advanceTime(1000);
@@ -661,10 +661,10 @@
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device1)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device2)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(2, false);
advanceTime(1000);
@@ -674,10 +674,10 @@
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device1)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device2)
- .getBrightnessThrottlingDataIdLocked());
+ .getThermalBrightnessThrottlingDataIdLocked());
}
@Test
@@ -863,7 +863,7 @@
layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThrottlingMapId= */ null);
+ /* refreshRateThermalThrottlingMapId= */ null);
}
private void advanceTime(long timeMs) {
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index db5a469..6907145 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -2583,7 +2583,7 @@
KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
}
- public void setBrightnessThrottlingData(String brightnessThrottlingData) {
+ public void setThermalBrightnessThrottlingData(String brightnessThrottlingData) {
putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
new file mode 100644
index 0000000..36c2001
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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.media.projection;
+
+
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.projection.IMediaProjectionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.view.ContentRecordingSession;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.wm.WindowManagerInternal;
+
+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 the {@link MediaProjectionManagerService} class.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:MediaProjectionManagerServiceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionManagerServiceTest {
+ private static final int UID = 10;
+ private static final String PACKAGE_NAME = "test.package";
+ private final ApplicationInfo mAppInfo = new ApplicationInfo();
+ private static final ContentRecordingSession DISPLAY_SESSION =
+ ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
+ // Callback registered by an app on a MediaProjection instance.
+ private final FakeIMediaProjectionCallback mIMediaProjectionCallback =
+ new FakeIMediaProjectionCallback();
+
+ private final MediaProjectionManagerService.Injector mPreventReusedTokenEnabledInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ boolean shouldMediaProjectionPreventReusingConsent(
+ MediaProjectionManagerService.MediaProjection projection) {
+ return true;
+ }
+ };
+
+ private final MediaProjectionManagerService.Injector mPreventReusedTokenDisabledInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ boolean shouldMediaProjectionPreventReusingConsent(
+ MediaProjectionManagerService.MediaProjection projection) {
+ return false;
+ }
+ };
+
+ private Context mContext;
+ private MediaProjectionManagerService mService;
+ private OffsettableClock mClock;
+ private ContentRecordingSession mWaitingDisplaySession =
+ ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
+
+ @Mock
+ private ActivityManagerInternal mAmInternal;
+ @Mock
+ private WindowManagerInternal mWindowManagerInternal;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
+
+ mContext = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+
+ mClock = new OffsettableClock.Stopped();
+ mWaitingDisplaySession.setWaitingToRecord(true);
+ mWaitingDisplaySession.setVirtualDisplayId(5);
+
+ mAppInfo.targetSdkVersion = 32;
+
+ mService = new MediaProjectionManagerService(mContext);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ }
+
+ @Test
+ public void testGetActiveProjectionInfoInternal() throws NameNotFoundException {
+ assertThat(mService.getActiveProjectionInfo()).isNull();
+
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+ // Create a projection, active is still null.
+ assertThat(projection).isNotNull();
+ assertThat(mService.getActiveProjectionInfo()).isNull();
+
+ // Start the projection, active is now not null.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ }
+
+ @Test
+ public void testCreateProjection() throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+ projection.start(mIMediaProjectionCallback);
+
+ MediaProjectionManagerService.MediaProjection secondProjection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+ assertThat(secondProjection).isNotNull();
+ assertThat(secondProjection).isNotEqualTo(projection);
+ }
+
+ @Test
+ public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+ projection.start(mIMediaProjectionCallback);
+
+ MediaProjectionManagerService.MediaProjection secondProjection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ true);
+
+ assertThat(secondProjection).isNotNull();
+ assertThat(secondProjection).isNotEqualTo(projection);
+ }
+
+ @Test
+ public void testCreateProjection_attemptReuse_priorProjectionGrant_notWaiting()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+ projection.start(mIMediaProjectionCallback);
+
+ // Mark this projection as not waiting.
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ mService.setContentRecordingSession(DISPLAY_SESSION);
+
+ // We are allowed to create another projection.
+ MediaProjectionManagerService.MediaProjection secondProjection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ true);
+
+ assertThat(secondProjection).isNotNull();
+
+ // But this is a new projection.
+ assertThat(secondProjection).isNotEqualTo(projection);
+ }
+
+ @Test
+ public void testCreateProjection_attemptReuse_priorProjectionGrant_waiting_differentPackage()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(/* packageAttemptedReusingGrantedConsent= */ false);
+ projection.start(mIMediaProjectionCallback);
+
+ // Mark this projection as not waiting.
+ mService.setContentRecordingSession(mWaitingDisplaySession);
+
+ // We are allowed to create another projection.
+ MediaProjectionManagerService.MediaProjection secondProjection =
+ mService.createProjectionInternal(UID + 10, PACKAGE_NAME + "foo",
+ TYPE_MIRRORING, /* isPermanentGrant= */ true,
+ UserHandle.CURRENT, /* packageAttemptedReusingGrantedConsent= */ true);
+
+ assertThat(secondProjection).isNotNull();
+
+ // But this is a new projection.
+ assertThat(secondProjection).isNotEqualTo(projection);
+ }
+
+ @Test
+ public void testIsValid_multipleStarts_preventionDisabled() throws NameNotFoundException {
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mPreventReusedTokenDisabledInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
+ service);
+ // No starts yet, and not timed out yet - so still valid.
+ assertThat(projection.isValid()).isTrue();
+
+ // Only one start - so still valid.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(projection.isValid()).isTrue();
+
+ // Second start - technically allowed to start again, without stopping in between.
+ // Token should no longer be valid.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(projection.isValid()).isFalse();
+ }
+
+ @Test
+ public void testIsValid_restart() throws NameNotFoundException {
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mPreventReusedTokenDisabledInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
+ service);
+ // No starts yet, and not timed out yet - so still valid.
+ assertThat(projection.isValid()).isTrue();
+
+ // Only one start - so still valid.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(projection.isValid()).isTrue();
+
+ projection.stop();
+
+ // Second start - so not valid.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(projection.isValid()).isFalse();
+ }
+
+ @Test
+ public void testIsValid_timeout() throws NameNotFoundException {
+ final MediaProjectionManagerService.Injector mClockInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ MediaProjectionManagerService.Clock createClock() {
+ // Always return the same value for elapsed time.
+ return () -> mClock.now();
+ }
+ @Override
+ boolean shouldMediaProjectionPreventReusingConsent(
+ MediaProjectionManagerService.MediaProjection projection) {
+ return false;
+ }
+ };
+ final MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mClockInjector);
+ MediaProjectionManagerService.MediaProjection projection = createProjectionPreconditions(
+ service);
+ mClock.fastForward(projection.mDefaultTimeoutMs + 10);
+
+ // Immediate timeout - so no longer valid.
+ assertThat(projection.isValid()).isFalse();
+ }
+
+ @Test
+ public void testIsValid_virtualDisplayAlreadyCreated() throws NameNotFoundException {
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mPreventReusedTokenDisabledInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
+ service);
+ // Simulate MediaProjection#createVirtualDisplay being invoked previously.
+ projection.notifyVirtualDisplayCreated(10);
+
+ // Trying to re-use token on another MediaProjection#createVirtualDisplay - no longer valid.
+ assertThat(projection.isValid()).isFalse();
+ }
+
+ // TODO(269273190): Test flag using compat annotations instead.
+ @Test
+ public void testIsValid_invalid_preventionEnabled()
+ throws NameNotFoundException {
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mPreventReusedTokenEnabledInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
+ service);
+ projection.start(mIMediaProjectionCallback);
+ projection.stop();
+ // Second start - so not valid.
+ projection.start(mIMediaProjectionCallback);
+
+ assertThrows(IllegalStateException.class, projection::isValid);
+ }
+
+ // TODO(269273190): Test flag using compat annotations instead.
+ @Test
+ public void testIsValid_invalid_preventionDisabled()
+ throws NameNotFoundException {
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
+ mPreventReusedTokenDisabledInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
+ service);
+ projection.start(mIMediaProjectionCallback);
+ projection.stop();
+
+ // Second start - so not valid.
+ projection.start(mIMediaProjectionCallback);
+
+ assertThat(projection.isValid()).isFalse();
+ }
+
+ @Test
+ public void testIsCurrentProjectionInternal_invalid() throws NameNotFoundException {
+ IBinder iBinder = mock(IBinder.class);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+ // Create a projection, current is false.
+ assertThat(projection).isNotNull();
+ assertThat(mService.isCurrentProjection(iBinder)).isFalse();
+
+ // Start the projection, and test a random token.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(mService.isCurrentProjection(iBinder)).isFalse();
+ }
+
+ @Test
+ public void testIsCurrentProjectionInternal_noProjection() {
+ IBinder iBinder = mock(IBinder.class);
+ assertThat(mService.isCurrentProjection(iBinder)).isFalse();
+ }
+
+ @Test
+ public void testIsCurrentProjectionInternal_currentProjection()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+ // Create a projection, current is false.
+ assertThat(projection).isNotNull();
+ assertThat(mService.isCurrentProjection(projection.asBinder())).isFalse();
+
+ // Start the projection, is current is now true.
+ projection.start(mIMediaProjectionCallback);
+ assertThat(mService.isCurrentProjection(projection.asBinder())).isTrue();
+ }
+
+ // Set up preconditions for creating a projection.
+ private MediaProjectionManagerService.MediaProjection createProjectionPreconditions(
+ MediaProjectionManagerService service)
+ throws NameNotFoundException {
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ return service.createProjectionInternal(UID, PACKAGE_NAME,
+ TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT,
+ /* packageAttemptedReusingGrantedConsent= */ false);
+ }
+
+ // Set up preconditions for creating a projection.
+ private MediaProjectionManagerService.MediaProjection createProjectionPreconditions()
+ throws NameNotFoundException {
+ return createProjectionPreconditions(mService);
+ }
+
+ // Set up preconditions for starting a projection, with no foreground service requirements.
+ private MediaProjectionManagerService.MediaProjection startProjectionPreconditions(
+ MediaProjectionManagerService service)
+ throws NameNotFoundException {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ return createProjectionPreconditions(service);
+ }
+
+ // Set up preconditions for starting a projection, specifying if it is possible to reuse the
+ // the current projection.
+ private MediaProjectionManagerService.MediaProjection startProjectionPreconditions(
+ boolean packageAttemptedReusingGrantedConsent)
+ throws NameNotFoundException {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+ any(ApplicationInfoFlags.class), any(UserHandle.class));
+ return mService.createProjectionInternal(UID, PACKAGE_NAME,
+ TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT,
+ packageAttemptedReusingGrantedConsent);
+ }
+
+ // Set up preconditions for starting a projection, with no foreground service requirements.
+ private MediaProjectionManagerService.MediaProjection startProjectionPreconditions()
+ throws NameNotFoundException {
+ mAppInfo.privateFlags |= PRIVATE_FLAG_PRIVILEGED;
+ return createProjectionPreconditions(mService);
+ }
+
+ private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
+ @Override
+ public void onStop() throws RemoteException {
+ }
+
+ @Override
+ public void onCapturedContentResize(int width, int height) throws RemoteException {
+ }
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) throws RemoteException {
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 0b6756d..6bcda3f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -178,6 +178,8 @@
UserHandle.of(userInfo.id));
assertThat(userContext.getSystemService(
UserManager.class).isMediaSharedWithParent()).isTrue();
+ assertThat(Settings.Secure.getInt(userContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0)).isEqualTo(1);
List<UserInfo> list = mUserManager.getUsers();
List<UserInfo> cloneUsers = list.stream().filter(
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index b2843d8..de82854 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -422,7 +422,8 @@
null /* splitNames */, null /* isFeatureSplits */, null /* usesSplitNames */,
null /* configForSplit */, null /* splitApkPaths */,
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
- null /* requiredSplitTypes */, null /* splitTypes */);
+ null /* requiredSplitTypes */, null /* splitTypes */,
+ false /* allowUpdateOwnership */);
Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
}
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 09ee598..f44c1d1 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -33,7 +33,8 @@
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<application android:debuggable="true">
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 3888b9b..eceb589 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -28,6 +28,7 @@
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -197,7 +198,6 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestablePermissions;
@@ -228,6 +228,7 @@
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.UiServiceTestCase;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -254,7 +255,6 @@
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -337,6 +337,8 @@
@Mock
ActivityManagerInternal mAmi;
@Mock
+ JobSchedulerInternal mJsi;
+ @Mock
private Looper mMainLooper;
@Mock
private NotificationManager mMockNm;
@@ -445,6 +447,8 @@
LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmi);
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
+ LocalServices.addService(JobSchedulerInternal.class, mJsi);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
@@ -1253,7 +1257,7 @@
waitForIdle();
update = new NotificationChannel("blockedbyuser", "name", IMPORTANCE_NONE);
- update.setFgServiceShown(true);
+ update.setUserVisibleTaskShown(true);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, update);
waitForIdle();
assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel(
@@ -4736,7 +4740,7 @@
}
@Test
- public void testBumpFGImportance_noChannelChangePreOApp() throws Exception {
+ public void testBumpFGImportance_channelChangePreOApp() throws Exception {
String preOPkg = PKG_N_MR1;
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -4754,7 +4758,7 @@
.setPriority(Notification.PRIORITY_MIN);
StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
- "testBumpFGImportance_noChannelChangePreOApp",
+ "testBumpFGImportance_channelChangePreOApp",
Binder.getCallingUid(), 0, nb.build(),
UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
@@ -4774,11 +4778,11 @@
.setPriority(Notification.PRIORITY_MIN);
sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
- "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(),
+ "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(),
0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
- "testBumpFGImportance_noChannelChangePreOApp",
+ "testBumpFGImportance_channelChangePreOApp",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
assertEquals(IMPORTANCE_LOW,
@@ -4786,7 +4790,7 @@
NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
preOPkg, mContext.getUserId(), preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID);
- assertEquals(IMPORTANCE_UNSPECIFIED, defaultChannel.getImportance());
+ assertEquals(IMPORTANCE_LOW, defaultChannel.getImportance());
}
@Test
@@ -6909,6 +6913,7 @@
public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_defaultDisplay()
throws Exception {
mockIsVisibleBackgroundUsersSupported(true);
+ mockIsUserVisible(SECONDARY_DISPLAY_ID, true);
mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
allowTestPackageToToast();
@@ -8610,7 +8615,7 @@
assertNotNull(n.publicVersion.bigContentView);
assertNotNull(n.publicVersion.headsUpContentView);
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
assertNull(n.contentView);
assertNull(n.bigContentView);
@@ -10440,7 +10445,7 @@
.setFullScreenIntent(mock(PendingIntent.class), true)
.build();
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
@@ -10525,7 +10530,7 @@
.setFlag(FLAG_CAN_COLORIZE, true)
.build();
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
assertFalse(n.isForegroundService());
assertFalse(n.hasColorizedPermission());
@@ -10553,7 +10558,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should not be set
assertSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10572,7 +10577,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be set
assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10596,7 +10601,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be set
assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10613,7 +10618,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should not be set
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10631,7 +10636,7 @@
n.flags |= Notification.FLAG_NO_DISMISS;
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be cleared
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10649,7 +10654,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should not be set
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10670,7 +10675,7 @@
n.flags |= Notification.FLAG_NO_DISMISS;
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be cleared
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10687,7 +10692,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should not be set
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10706,7 +10711,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be set
assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10734,7 +10739,7 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should be cleared
assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10755,12 +10760,589 @@
.build();
// When: fix the notification with NotificationManagerService
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
// Then: the notification's flag FLAG_NO_DISMISS should not be set
assertSame(0, n.flags & Notification.FLAG_NO_DISMISS);
}
+ @Test
+ public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCancelAllNotifications_IgnoreUserInitiatedJob",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCancelAllNotifications_UijFlag_NoUij_Allowed() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(false);
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCancelAllNotifications_UijFlag_NoUij_Allowed",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelAllNotificationsOtherPackage_IgnoresUijNotification() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCancelAllNotifications_IgnoreOtherPackages",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testRemoveUserInitiatedJobFlag_ImmediatelyAfterEnqueue() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mInternalService.removeUserInitiatedJobFlagFromNotification(PKG, sbn.getId(),
+ sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertFalse(notifs[0].getNotification().isUserInitiatedJob());
+ }
+
+ @Test
+ public void testCancelAfterSecondEnqueueDoesNotSpecifyUserInitiatedJobFlag() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT | FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
+ sbn.getUserId());
+ waitForIdle();
+ assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCancelNotificationWithTag_fromApp_cannotCancelUijChild() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ mService.isSystemUid = false;
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.getBinderService().cancelNotificationWithTag(
+ parent.getSbn().getPackageName(), parent.getSbn().getPackageName(),
+ parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationWithTag_fromApp_cannotCancelUijParent() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ mService.isSystemUid = false;
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.getBinderService().cancelNotificationWithTag(
+ parent.getSbn().getPackageName(), parent.getSbn().getPackageName(),
+ parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(3, notifs.length);
+ }
+
+ @Test
+ public void testCancelAllNotificationsFromApp_cannotCancelUijChild() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ mService.isSystemUid = false;
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ mService.getBinderService().cancelAllNotifications(
+ parent.getSbn().getPackageName(), parent.getSbn().getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testCancelAllNotifications_fromApp_cannotCancelUijParent() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ mService.isSystemUid = false;
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ mService.getBinderService().cancelAllNotifications(
+ parent.getSbn().getPackageName(), parent.getSbn().getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_clearAll_GroupWithUijParent() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ mService.getBinderService().cancelNotificationsFromListener(null, null);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_clearAll_GroupWithUijChild() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ mService.getBinderService().cancelNotificationsFromListener(null, null);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_clearAll_Uij() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, null, false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mService.addNotification(child2);
+ mService.getBinderService().cancelNotificationsFromListener(null, null);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(child2.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_byKey_GroupWithUijParent() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+ child2.getSbn().getKey(), newGroup.getSbn().getKey()};
+ mService.getBinderService().cancelNotificationsFromListener(null, keys);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_byKey_GroupWithUijChild() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+ child2.getSbn().getKey(), newGroup.getSbn().getKey()};
+ mService.getBinderService().cancelNotificationsFromListener(null, keys);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_byKey_Uij() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 3, null, false);
+ child.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mService.addNotification(child);
+ String[] keys = {child.getSbn().getKey()};
+ mService.getBinderService().cancelNotificationsFromListener(null, keys);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(child.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testUserInitiatedCancelAllWithGroup_UserInitiatedFlag() throws Exception {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+ final NotificationRecord child2 = generateNotificationRecord(
+ mTestNotificationChannel, 3, "group", false);
+ child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ final NotificationRecord newGroup = generateNotificationRecord(
+ mTestNotificationChannel, 4, "group2", false);
+ mService.addNotification(parent);
+ mService.addNotification(child);
+ mService.addNotification(child2);
+ mService.addNotification(newGroup);
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), parent.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ public void testDeleteChannelGroupChecksForUijs() throws Exception {
+ when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ CountDownLatch latch = new CountDownLatch(2);
+ mService.createNotificationChannelGroup(PKG, mUid,
+ new NotificationChannelGroup("group", "group"), true, false);
+ new Thread(() -> {
+ NotificationChannel notificationChannel = new NotificationChannel("id", "id",
+ NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("group");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ latch.countDown();
+ }).start();
+ new Thread(() -> {
+ try {
+ synchronized (this) {
+ wait(5000);
+ }
+ mService.createNotificationChannelGroup(PKG, mUid,
+ new NotificationChannelGroup("new", "new group"), true, false);
+ NotificationChannel notificationChannel =
+ new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("new");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ latch.countDown();
+ }).start();
+
+ latch.await();
+ verify(mJsi).isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+ anyString(), anyInt(), anyString());
+ }
+
+ @Test
+ public void testRemoveUserInitiatedJobFlagFromNotification_enqueued() {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_USER_INITIATED_JOB;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+
+ mInternalService.removeUserInitiatedJobFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ verify(mListeners, timeout(200).times(0)).notifyPostedLocked(any(), any());
+ }
+
+ @Test
+ public void testRemoveUserInitiatedJobFlagFromNotification_posted() {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_USER_INITIATED_JOB;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mInternalService.removeUserInitiatedJobFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
+
+ assertEquals(0, captor.getValue().getNotification().flags);
+ }
+
+ @Test
+ public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_enqueued() {
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ Notification n = new Notification.Builder(mContext, "").build();
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addEnqueuedNotification(r);
+ }
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_USER_INITIATED_JOB;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+ NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+
+ mInternalService.removeUserInitiatedJobFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_posted() {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ Notification n = new Notification.Builder(mContext, "").build();
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addNotification(r);
+ }
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_USER_INITIATED_JOB;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+ NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mInternalService.removeUserInitiatedJobFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCanPostUijWhenOverLimit() throws RemoteException {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+ i, null, false).getSbn();
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCanPostUijWhenOverLimit",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ }
+
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCanPostUijWhenOverLimit - uij over limit!",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+
+ waitForIdle();
+
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length);
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCannotPostNonUijWhenOverLimit() throws RemoteException {
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(true);
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+ i, null, false).getSbn();
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCannotPostNonUijWhenOverLimit",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+ }
+
+ final StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+ 100, null, false).getSbn();
+ sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCannotPostNonUijWhenOverLimit - uij over limit!",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+
+ final StatusBarNotification sbn2 = generateNotificationRecord(mTestNotificationChannel,
+ 101, null, false).getSbn();
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCannotPostNonUijWhenOverLimit - non uij over limit!",
+ sbn2.getId(), sbn2.getNotification(), sbn2.getUserId());
+
+ when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+ .thenReturn(false);
+ final StatusBarNotification sbn3 = generateNotificationRecord(mTestNotificationChannel,
+ 101, null, false).getSbn();
+ sbn3.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCannotPostNonUijWhenOverLimit - fake uij over limit!",
+ sbn3.getId(), sbn3.getNotification(), sbn3.getUserId());
+
+ waitForIdle();
+
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length);
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void fixNotification_withUijFlag_butIsNotUij() throws Exception {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+
+ Notification n = new Notification.Builder(mContext, "test")
+ .setFlag(FLAG_USER_INITIATED_JOB, true)
+ .build();
+
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+ assertFalse(n.isUserInitiatedJob());
+ }
+
private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 893f538..3ba9400 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -120,12 +120,20 @@
.showLights(false)
.showBadges(false)
.showInAmbientDisplay(false)
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+ .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
+ .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE)
.build();
ZenModeConfig config = getMutedAllConfig();
config.allowAlarms = true;
config.allowReminders = true;
config.allowEvents = true;
+ config.allowCalls = true;
+ config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS;
+ config.allowMessages = true;
+ config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED;
+ config.allowConversations = false;
config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -138,6 +146,10 @@
assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents());
assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights());
assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient());
+ assertEquals(expected.getPriorityConversationSenders(),
+ actual.getPriorityConversationSenders());
+ assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
+ assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
}
@Test
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 593ee4a..2696d2b 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -42,7 +42,8 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/>
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index e85b574..5282585e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -147,8 +147,8 @@
int width = 100;
int height = 300;
- Rect bounds = new Rect(0, 0, width, height);
- mDimmer.updateDims(mTransaction, bounds);
+ mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+ mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
verify(mTransaction).show(getDimLayer());
@@ -194,7 +194,7 @@
SurfaceControl dimLayer = getDimLayer();
mDimmer.resetDimStates();
- mDimmer.updateDims(mTransaction, new Rect());
+ mDimmer.updateDims(mTransaction);
verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
@@ -212,29 +212,29 @@
mDimmer.resetDimStates();
mDimmer.dimAbove(mTransaction, child, alpha);
- mDimmer.updateDims(mTransaction, new Rect());
+ mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction, never()).remove(dimLayer);
}
@Test
public void testDimUpdateWhileDimming() {
- Rect bounds = new Rect();
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
final float alpha = 0.8f;
mDimmer.dimAbove(mTransaction, child, alpha);
+ final Rect bounds = mDimmer.mDimState.mDimBounds;
SurfaceControl dimLayer = getDimLayer();
bounds.set(0, 0, 10, 10);
- mDimmer.updateDims(mTransaction, bounds);
+ mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
verify(mTransaction, times(1)).show(dimLayer);
verify(mTransaction).setPosition(dimLayer, 0, 0);
bounds.set(10, 10, 30, 30);
- mDimmer.updateDims(mTransaction, bounds);
+ mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
verify(mTransaction).setPosition(dimLayer, 10, 10);
}
@@ -246,13 +246,13 @@
mDimmer.dimAbove(mTransaction, child, 1);
SurfaceControl dimLayer = getDimLayer();
- mDimmer.updateDims(mTransaction, new Rect());
+ mDimmer.updateDims(mTransaction);
verify(mTransaction, times(1)).show(dimLayer);
reset(mSurfaceAnimatorStarter);
mDimmer.dontAnimateExit();
mDimmer.resetDimStates();
- mDimmer.updateDims(mTransaction, new Rect());
+ mDimmer.updateDims(mTransaction);
verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 146ed34..495f868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -62,6 +62,7 @@
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
+import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.DisplayAddress;
@@ -76,6 +77,9 @@
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+import com.android.server.wm.DisplayContent.FixedRotationTransitionListener;
import org.junit.After;
import org.junit.AfterClass;
@@ -104,6 +108,9 @@
private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50;
private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
+ private static final OffsettableClock sClock = new OffsettableClock.Stopped();
+ private static TestHandler sHandler;
+ private static long sCurrentUptimeMillis = 10_000;
private static WindowManagerService sMockWm;
private DisplayContent mMockDisplayContent;
@@ -113,6 +120,7 @@
private Resources mMockRes;
private SensorManager mMockSensorManager;
private Sensor mFakeOrientationSensor;
+ private Sensor mFakeHingeAngleSensor;
private DisplayWindowSettings mMockDisplayWindowSettings;
private ContentResolver mMockResolver;
private FakeSettingsProvider mFakeSettingsProvider;
@@ -125,6 +133,9 @@
private ContentObserver mUserRotationObserver;
private SensorEventListener mOrientationSensorListener;
+ ArgumentCaptor<SensorEventListener> mHingeAngleSensorListenerCaptor = ArgumentCaptor.forClass(
+ SensorEventListener.class);
+
private DisplayRotationBuilder mBuilder;
private DeviceStateController mDeviceStateController;
@@ -135,6 +146,7 @@
sMockWm = mock(WindowManagerService.class);
sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
sMockWm.mPolicy = mock(WindowManagerPolicy.class);
+ sHandler = new TestHandler(null, sClock);
}
@AfterClass
@@ -468,12 +480,16 @@
}
private SensorEvent createSensorEvent(int rotation) throws Exception {
+ return createSensorEvent(mFakeOrientationSensor, rotation);
+ }
+
+ private SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
final Constructor<SensorEvent> constructor =
SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(1);
- event.sensor = mFakeOrientationSensor;
- event.values[0] = rotation;
+ event.sensor = sensor;
+ event.values[0] = value;
event.timestamp = SystemClock.elapsedRealtimeNanos();
return event;
}
@@ -943,6 +959,120 @@
Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
}
+ @Test
+ public void testSensorRotationAfterDisplayChangeBeforeTimeout_ignoresSensor() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(true)
+ .setDisplaySwitchRotationBlockTimeMs(1000)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ mTarget.physicalDisplayChanged();
+
+ moveTimeForward(900);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testSensorRotationAfterDisplayChangeAfterTimeout_usesSensor() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(true)
+ .setDisplaySwitchRotationBlockTimeMs(1000)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ mTarget.physicalDisplayChanged();
+
+ moveTimeForward(1100);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testSensorRotationAfterHingeEventBeforeTimeout_ignoresSensor() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(true)
+ .setMaxHingeAngle(165)
+ .setHingeAngleRotationBlockTimeMs(400)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ sendHingeAngleEvent(130);
+
+ moveTimeForward( 300);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testSensorRotationAfterHingeEventBeforeTimeoutFlagDisabled_usesSensorData()
+ throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(false)
+ .setMaxHingeAngle(165)
+ .setHingeAngleRotationBlockTimeMs(400)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ sendHingeAngleEvent(130);
+
+ moveTimeForward( 300);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
+ @Test
+ public void testSensorRotationAfterHingeEventAfterTimeout_usesSensorData() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(true)
+ .setMaxHingeAngle(165)
+ .setHingeAngleRotationBlockTimeMs(400)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ sendHingeAngleEvent(180);
+
+ moveTimeForward(1010);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
+
+ @Test
+ public void testSensorRotationAfterLargeHingeEventBeforeTimeout_usesSensor() throws Exception {
+ mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ .setPauseRotationWhenUnfolding(true)
+ .setMaxHingeAngle(165)
+ .setHingeAngleRotationBlockTimeMs(400)
+ .build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+ thawRotation();
+ enableOrientationSensor();
+
+ sendHingeAngleEvent(180);
+
+ moveTimeForward(300);
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+ SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+ }
+
// ========================
// Non-rotation API Tests
// ========================
@@ -963,6 +1093,12 @@
+ " fixed to user rotation.", mTarget.isFixedToUserRotation());
}
+ private void moveTimeForward(long timeMillis) {
+ sCurrentUptimeMillis += timeMillis;
+ sClock.fastForward(timeMillis);
+ sHandler.timeAdvance();
+ }
+
/**
* Call {@link DisplayRotation#configure(int, int)} to configure {@link #mTarget}
* according to given parameters.
@@ -995,6 +1131,17 @@
mTarget.configure(width, height);
}
+ private void sendHingeAngleEvent(int hingeAngle) {
+ mHingeAngleSensorListenerCaptor.getAllValues().forEach(sensorEventListener -> {
+ try {
+ sensorEventListener.onSensorChanged(createSensorEvent(mFakeHingeAngleSensor,
+ hingeAngle));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
private void freezeRotation(int rotation) {
mTarget.freezeRotation(rotation);
@@ -1016,7 +1163,11 @@
private class DisplayRotationBuilder {
private boolean mIsDefaultDisplay = true;
private boolean mSupportAutoRotation = true;
+ private boolean mPauseRotationWhenUnfolding = false;
private boolean mSupportHalfFoldAutoRotateOverride = false;
+ private int mDisplaySwitchRotationBlockTimeMs;
+ private int mHingeAngleRotationBlockTimeMs;
+ private int mMaxHingeAngle;
private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
private int mCarDockRotation;
@@ -1028,6 +1179,29 @@
return this;
}
+ public DisplayRotationBuilder setPauseRotationWhenUnfolding(
+ boolean pauseRotationWhenUnfolding) {
+ mPauseRotationWhenUnfolding = pauseRotationWhenUnfolding;
+ return this;
+ }
+
+ public DisplayRotationBuilder setDisplaySwitchRotationBlockTimeMs(
+ int displaySwitchRotationBlockTimeMs) {
+ mDisplaySwitchRotationBlockTimeMs = displaySwitchRotationBlockTimeMs;
+ return this;
+ }
+
+ public DisplayRotationBuilder setHingeAngleRotationBlockTimeMs(
+ int hingeAngleRotationBlockTimeMs) {
+ mHingeAngleRotationBlockTimeMs = hingeAngleRotationBlockTimeMs;
+ return this;
+ }
+
+ public DisplayRotationBuilder setMaxHingeAngle(int maxHingeAngle) {
+ mMaxHingeAngle = maxHingeAngle;
+ return this;
+ }
+
private DisplayRotationBuilder setSupportAutoRotation(boolean supportAutoRotation) {
mSupportAutoRotation = supportAutoRotation;
return this;
@@ -1153,10 +1327,27 @@
when(mMockDisplayContent.getWindowConfiguration())
.thenReturn(new WindowConfiguration());
+ Field field = DisplayContent.class
+ .getDeclaredField("mFixedRotationTransitionListener");
+ field.setAccessible(true);
+ field.set(mMockDisplayContent, mock(FixedRotationTransitionListener.class));
+
mMockDisplayPolicy = mock(DisplayPolicy.class);
mMockRes = mock(Resources.class);
when(mMockContext.getResources()).thenReturn((mMockRes));
+ when(mMockRes.getBoolean(com.android.internal.R.bool
+ .config_windowManagerPauseRotationWhenUnfolding))
+ .thenReturn(mPauseRotationWhenUnfolding);
+ when(mMockRes.getInteger(com.android.internal.R.integer
+ .config_pauseRotationWhenUnfolding_displaySwitchTimeout))
+ .thenReturn(mDisplaySwitchRotationBlockTimeMs);
+ when(mMockRes.getInteger(com.android.internal.R.integer
+ .config_pauseRotationWhenUnfolding_hingeEventTimeout))
+ .thenReturn(mHingeAngleRotationBlockTimeMs);
+ when(mMockRes.getInteger(com.android.internal.R.integer
+ .config_pauseRotationWhenUnfolding_maxHingeAngle))
+ .thenReturn(mMaxHingeAngle);
when(mMockRes.getBoolean(com.android.internal.R.bool.config_supportAutoRotation))
.thenReturn(mSupportAutoRotation);
when(mMockRes.getInteger(com.android.internal.R.integer.config_lidOpenRotation))
@@ -1169,11 +1360,16 @@
.thenReturn(convertRotationToDegrees(mUndockedHdmiRotation));
mMockSensorManager = mock(SensorManager.class);
+ when(mMockContext.getSystemService(SensorManager.class))
+ .thenReturn(mMockSensorManager);
when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
.thenReturn(mMockSensorManager);
mFakeOrientationSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
Collections.singletonList(mFakeOrientationSensor));
+ mFakeHingeAngleSensor = mock(Sensor.class);
+ when(mMockSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)).thenReturn(
+ mFakeHingeAngleSensor);
when(mMockContext.getResources().getBoolean(
com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
@@ -1200,6 +1396,10 @@
reset(sMockWm);
+ verify(mMockSensorManager, atLeast(0)).registerListener(
+ mHingeAngleSensorListenerCaptor.capture(), eq(mFakeHingeAngleSensor), anyInt(),
+ any());
+
captureObservers();
}
}
@@ -1226,5 +1426,15 @@
mProposedRotationCallback.accept(rotation);
}
}
+
+ @Override
+ Handler getHandler() {
+ return sHandler;
+ }
+
+ @Override
+ long uptimeMillis() {
+ return sCurrentUptimeMillis;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 43fc1c4..7cb7c79d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -111,7 +111,6 @@
mDisplayUniqueId = "test:" + sNextUniqueId++;
mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setUniqueId(mDisplayUniqueId).build();
- mTestDisplay.getDefaultTaskDisplayArea().setWindowingMode(TEST_WINDOWING_MODE);
when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
.thenReturn(mTestDisplay);
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 12e4825..a15ee69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -41,6 +41,7 @@
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_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -146,7 +147,7 @@
mActivity = setUpActivityWithComponent();
mController = new LetterboxUiController(mWm, mActivity);
prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
- mController.setRelauchingAfterRequestedOrientationChanged(false);
+ mController.setRelaunchingAfterRequestedOrientationChanged(false);
spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
@@ -190,6 +191,8 @@
@Test
public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
// Request 3 times to simulate orientation request loop
for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) {
@@ -200,8 +203,30 @@
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED})
+ public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mockThatProperty(PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED,
+ /* value */ false);
+ doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ // Request 3 times to simulate orientation request loop
+ for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) {
+ assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false,
+ /* expectedCount */ 0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED})
public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+
// Request 3 times to simulate orientation request loop
for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) {
assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false,
@@ -212,7 +237,10 @@
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED})
public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+
// No orientation request loop
assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false,
/* expectedCount */ 0);
@@ -222,7 +250,10 @@
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED})
public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse()
throws InterruptedException {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+
for (int i = MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i > 0; i--) {
assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false,
/* expectedCount */ 0);
@@ -233,7 +264,10 @@
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED})
public void testShouldIgnoreOrientationRequestLoop_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+
for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) {
assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false,
/* expectedCount */ i);
@@ -870,7 +904,7 @@
private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() {
doReturn(true).when(mLetterboxConfiguration)
.isPolicyForIgnoringRequestedOrientationEnabled();
- mController.setRelauchingAfterRequestedOrientationChanged(true);
+ mController.setRelaunchingAfterRequestedOrientationChanged(true);
}
private ActivityRecord setUpActivityWithComponent() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 9ebc730..10f4158 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -924,6 +924,11 @@
@Test
public void testFreezeTaskListOrder_timeout() {
+ for (Task t : mTasks) {
+ // Make all the tasks non-empty
+ new ActivityBuilder(mAtm).setTask(t).build();
+ }
+
// Add some tasks
mRecentTasks.add(mTasks.get(0));
mRecentTasks.add(mTasks.get(1));
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 a646d01..e96d1ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -813,6 +813,8 @@
spyOn(mActivity.mLetterboxUiController);
doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSurfaceReadyToShow(any());
+ doReturn(true).when(mActivity.mLetterboxUiController)
.isSurfaceVisible(any());
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b48fd7d..fdb3502 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -691,6 +691,7 @@
// Child window without scale (e.g. different app) should apply inverse scale of parent.
doReturn(1f).when(cmp).getCompatScale(anyString(), anyInt());
final WindowState child2 = createWindow(w, TYPE_APPLICATION_SUB_PANEL, "child2");
+ makeWindowVisible(w, child2);
clearInvocations(t);
child2.prepareSurfaces();
verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8948e494..a98429a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -46,6 +46,7 @@
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
+import android.app.UidObserver;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
@@ -592,33 +593,17 @@
}
}
- private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ private final IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
}
@Override
- public void onUidIdle(int uid, boolean disabled) {
- // Ignored
- }
-
- @Override
public void onUidGone(int uid, boolean disabled) {
onUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
}
-
- @Override
- public void onUidActive(int uid) {
- // Ignored
- }
-
- @Override public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override public void onUidProcAdjChanged(int uid) {
- }
};
@Override
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index 1a0f192..f3ef834 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -58,6 +58,9 @@
/** @hide **/
public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
+ /** @hide **/
+ public static final String CALLER_PID = "CallerPid";
+
private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
@NonNull CharSequence displayName,
@NonNull Uri address,
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index cfebf34..4299e0d6 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -87,7 +87,7 @@
// avoid flakiness we run these tests multiple times, allowing progressively longer between
// code loading and checking the logs on each try.)
private static final int AUDIT_LOG_RETRIES = 10;
- private static final int RETRY_DELAY_MS = 2_000;
+ private static final int RETRY_DELAY_MS = 500;
private static Context sContext;
private static int sMyUid;
@@ -253,7 +253,7 @@
"/DynamicCodeLoggerNativeExecutable", privateCopyFile);
EventLog.writeEvent(EventLog.getTagCode("auditd"),
- "type=1400 avc: granted { execute_no_trans } "
+ "type=1400 avc: granted { execute_no_trans } "
+ "path=\"" + privateCopyFile + "\" "
+ "scontext=u:r:untrusted_app: "
+ "tcontext=u:object_r:app_data_file: "
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 314b9e4..f389e13 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -260,7 +260,7 @@
snapshotLayers
.mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
.toTypedArray()
- val snapshotRegion = RegionSubject(visibleAreas, this, timestamp)
+ val snapshotRegion = RegionSubject(visibleAreas, timestamp)
val appVisibleRegion = it.visibleRegion(component)
if (snapshotRegion.region.isNotEmpty) {
snapshotRegion.coversExactly(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 47b2cda..5dc2dd7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -39,27 +40,20 @@
* Launch an app [testApp] and wait animation to complete
* Press back button
* ```
- *
* To run only the presubmit assertions add: `--
- *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
- *
* To run only the postsubmit assertions add: `--
- *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
- *
* To run only the flaky assertions add: `--
- *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -71,6 +65,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
index 70eedd9..9fa84019 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.close
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -24,6 +25,7 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index d8abb4e..b042a14 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -39,27 +40,20 @@
* Launch an app [testApp] and wait animation to complete
* Press home button
* ```
- *
* To run only the presubmit assertions add: `--
- *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
- *
* To run only the postsubmit assertions add: `--
- *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
- *
* To run only the flaky assertions add: `--
- *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -71,6 +65,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
index c74f54b..136995a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.close
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -24,6 +25,7 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
index a8f1b3d..6b24598 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -20,6 +20,7 @@
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.SystemClock;
+import android.util.Log;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -32,6 +33,7 @@
* Injects gestures given an {@link Instrumentation} object.
*/
public class GestureHelper {
+ private static final String TAG = GestureHelper.class.getSimpleName();
// Inserted after each motion event injection.
private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
@@ -208,6 +210,9 @@
for (int j = 0; j < coords.length; j++) {
coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
+
+ // TODO: remove logging once b/269505548 is resolved
+ Log.d(TAG, "(" + coords[j].x + ", " + coords[j].y + ")");
}
eventTime = SystemClock.uptimeMillis();
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 6066d2e..34fa921 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -88,7 +88,7 @@
imeSnapshotLayers
.mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion }
.toTypedArray()
- val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp)
+ val imeVisibleRegion = RegionSubject(visibleAreas, timestamp)
val appVisibleRegion = it.visibleRegion(imeTestApp)
if (imeVisibleRegion.region.isNotEmpty) {
imeVisibleRegion.coversAtMost(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 2fff001..d5208e0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -73,7 +73,16 @@
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
- super.cujCompleted()
+ runAndIgnoreAssumptionViolation { entireScreenCovered() }
+ runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() }
imeLayerBecomesInvisible()
imeWindowBecomesInvisible()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
index ac05c76..3289bc6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
@@ -41,4 +41,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
index 09c17b1..ccbe74f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -18,6 +18,7 @@
import android.platform.test.annotations.FlakyTest
import android.tools.common.NavBar
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -28,6 +29,7 @@
import org.junit.runners.Parameterized
/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 5cacb04..d0dc42f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -18,13 +18,14 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,7 +42,6 @@
* Make sure no apps are running on the device
* Launch an app [testApp] and wait animation to complete
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -53,6 +53,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
index f77f968..f75d9ee 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -26,6 +27,7 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -47,4 +49,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
index 8b4a613..4aa78d4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
@@ -44,4 +44,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 66e0f06..00d7544 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,6 +19,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -43,7 +44,6 @@
* Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
* complete (only this action is traced)
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -55,6 +55,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
index 8139e1f..ff24190 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -25,6 +26,7 @@
import org.junit.runners.Parameterized
/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 14df84e..9ab6156 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -22,6 +22,7 @@
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -47,7 +48,6 @@
* Lock the device.
* Launch an app on top of the lock screen [testApp] and wait animation to complete
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -59,6 +59,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index cfc8e46..cdd2d45 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -18,6 +18,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -41,7 +42,6 @@
* Press home
* Relaunch an app [testApp] and wait animation to complete (only this action is traced)
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -53,6 +53,7 @@
* ```
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
index b47c931..9679059 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -24,6 +25,7 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -41,4 +43,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
index e876e57..786bb32 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -24,10 +24,10 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import android.view.KeyEvent
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,7 +44,6 @@
* Make sure no apps are running on the device
* Launch an app [testApp] and wait animation to complete
* ```
- *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index 6cbb975..b848e63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -27,13 +27,13 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.R
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp
index 5f03aa2..a5c6d65 100644
--- a/tests/OdmApps/Android.bp
+++ b/tests/OdmApps/Android.bp
@@ -28,5 +28,6 @@
test_suites: ["device-tests"],
data: [
":TestOdmApp",
+ ":TestOdmPrivApp",
],
}